This commit is contained in:
mathuo 2020-08-29 10:20:12 +01:00
parent 4033489e38
commit 47f27633da
33 changed files with 516 additions and 177 deletions

View File

@ -2,7 +2,7 @@ import * as React from "react";
import { LoadFromConfig } from "./loadFromConfig";
import { FromApi } from "./fromApi";
import { PaneDemo } from "./pane";
import { TestGrid } from "./reactgrid";
import { TestGrid } from "./layout-grid/reactgrid";
const options = [
{ id: "config", component: LoadFromConfig },

View File

@ -0,0 +1,6 @@
import * as React from "react";
import { IPanelProps } from "splitview";
export const CustomTab = (props: IPanelProps) => {
return <div>hello</div>;
};

View File

@ -9,6 +9,39 @@ import {
CompositeDisposable,
GroupChangeKind,
} from "splitview";
import { CustomTab } from "./customTab";
import { SplitPanel } from "./splitPanel";
const Editor = (props: IPanelProps & { layoutApi: Api }) => {
const [tabHeight, setTabHeight] = React.useState<number>(0);
React.useEffect(() => {
if (props.layoutApi) {
setTabHeight(props.layoutApi.getTabHeight());
}
}, [props.layoutApi]);
const onTabHeightChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(event.target.value);
if (!Number.isNaN(value)) {
setTabHeight(value);
}
};
const onClick = () => {
props.layoutApi.setTabHeight(tabHeight);
};
return (
<div style={{ height: "100%", backgroundColor: "white", color: "black" }}>
<label>
Tab height
<input onChange={onTabHeightChange} value={tabHeight} type="number" />
<button onClick={onClick}>Apply</button>
</label>
</div>
);
};
const components = {
inner_component: (props: IPanelProps) => {
@ -122,6 +155,7 @@ const components = {
const backgroundColor = React.useMemo(
() =>
// "#1e1e1e",
`rgb(${Math.floor(Math.random() * 256)},${Math.floor(
Math.random() * 256
)},${Math.floor(Math.random() * 256)})`,
@ -143,6 +177,12 @@ const components = {
</div>
);
},
editor: Editor,
split_panel: SplitPanel,
};
const tabComponents = {
default: CustomTab,
};
const nextGuid = (() => {
@ -177,7 +217,7 @@ export const TestGrid = () => {
title: "Item 2",
});
api.addPanelFromComponent({
componentName: "test_component",
componentName: "split_panel",
id: nextGuid(),
title: "Item 3 with a long title",
});
@ -202,6 +242,18 @@ export const TestGrid = () => {
componentName: "test_component",
};
});
api.addDndHandle("Files", (ev) => {
const { event } = ev;
ev.event.event.preventDefault();
return {
id: Date.now().toString(),
title: event.event.dataTransfer.files[0].name,
componentName: "test_component",
};
});
}, [api]);
const onAdd = () => {
@ -224,6 +276,7 @@ export const TestGrid = () => {
_api.current?.layout(width, height);
};
window.addEventListener("resize", callback);
callback(undefined);
const dis = _api.current.onDidLayoutChange((event) => {
console.log(event.kind);
@ -280,7 +333,6 @@ export const TestGrid = () => {
if (!api) {
return;
}
console.log("create drag refs");
api.createDragTarget(
{ element: dragRef.current, content: "drag me" },
() => ({
@ -294,10 +346,25 @@ export const TestGrid = () => {
event.dataTransfer.setData("text/plain", "Panel2");
};
const onAddEditor = () => {
api.addPanelFromComponent({
id: "editor",
componentName: "editor",
tabComponentName: "default",
params: { layoutApi: api },
});
};
const onTabContextMenu = (event: MouseEvent) => {};
return (
<div style={{ width: "100%" }}>
<div
// className="visual-studio-theme"
style={{ width: "100%" }}
>
<div style={{ height: "20px", display: "flex" }}>
<button onClick={onAdd}>Add</button>
<button onClick={onAddEditor}>Expr</button>
<button onClick={onAddEmpty}>Add empty</button>
<button onClick={onConfig}>Save</button>
<button onClick={onLoad}>Load</button>
@ -335,9 +402,12 @@ export const TestGrid = () => {
// autoSizeToFitContainer={true}
onReady={onReady}
components={components}
tabComponents={tabComponents}
debug={true}
// tabHeight={30}
enableExternalDragEvents={true}
// serializedLayout={data}
// onTabContextMenu={onTabContextMenu}
/>
</div>
);

View File

@ -0,0 +1,43 @@
import * as React from "react";
import {
IPanelProps,
Orientation,
SplitviewFacade,
SplitviewReadyEvent,
} from "splitview";
import { SplitViewComponent } from "splitview";
const components = {
default1: (props) => {
return <div style={{ height: "100%", width: "100%" }}>hiya</div>;
},
};
export const SplitPanel = (props: IPanelProps) => {
const api = React.useRef<SplitviewFacade>();
React.useEffect(() => {
props.api.onDidPanelDimensionChange((event) => {
// const [height,width] = [event.height, event.width]
// const [size, orthogonalSize] =
// props.orientation === Orientation.HORIZONTAL
// ? [width, height]
// : [height, width];
api.current?.layout(event.width, event.height);
});
}, []);
const onReady = (event: SplitviewReadyEvent) => {
event.api.addFromComponent({ id: "1", component: "default1" });
event.api.addFromComponent({ id: "2", component: "default1" });
api.current = event.api;
};
return (
<SplitViewComponent
components={components}
onReady={onReady}
orientation={Orientation.VERTICAL}
/>
);
};

View File

@ -16,7 +16,7 @@ export interface IPaneComponentRef {
layout: (size: number, orthogonalSize: number) => void;
}
export type PaneComponent = React.RefForwardingComponent<
export type PaneComponent = React.ForwardRefRenderFunction<
IPaneComponentRef,
IPaneComponentProps
>;
@ -27,7 +27,7 @@ export interface IPaneHeaderComponentProps extends IViewWithReactComponent {
userprops?: { [index: string]: any };
}
export type PaneHeaderComponent = React.RefForwardingComponent<
export type PaneHeaderComponent = React.ForwardRefRenderFunction<
{},
IPaneHeaderComponentProps
>;

View File

@ -12,7 +12,7 @@ export interface IViewComponentRef {
layout: (size: number, orthogonalSize: number) => void;
}
export type ViewComponent = React.RefForwardingComponent<
export type ViewComponent = React.ForwardRefRenderFunction<
IViewComponentRef,
IViewComponentProps
>;

View File

@ -24,19 +24,19 @@ export interface IPaneViewReactProps {
initialLayout?: PaneViewSerializedConfig;
}
export type PaneViewReadyEvent = {
export interface PaneViewReadyEvent {
api: PaneviewApi;
};
}
export type PaneViewSerializedConfig = {
export interface PaneViewSerializedConfig {
views: Array<
Omit<IPaneWithReactComponent, "component" | "headerComponent"> & {
size?: number;
}
>;
};
}
export type PaneviewApi = {
export interface PaneviewApi {
add: (
options: Omit<IPaneWithReactComponent, "component" | "headerComponent"> & {
size?: number;
@ -45,7 +45,7 @@ export type PaneviewApi = {
) => void;
moveView: (from: number, to: number) => void;
toJSON: () => {};
};
}
export interface IPaneViewComponentRef {
layout: (size: number, orthogonalSize: number) => void;

View File

@ -9,15 +9,15 @@ export interface IViewWithReactComponent extends IBaseView {
component: ViewComponent;
}
export type OnReadyEvent = {
export interface OnReadyEvent {
api: SplitviewApi;
};
}
export type SerializedConfig = {
export interface SerializedConfig {
views: Array<Omit<IViewWithReactComponent, "component"> & { size?: number }>;
};
}
export type SplitviewApi = {
export interface SplitviewApi {
add: (
options: Omit<IViewWithReactComponent, "component"> & {
size?: number;
@ -26,7 +26,7 @@ export type SplitviewApi = {
) => void;
moveView: (from: number, to: number) => void;
toJSON: () => {};
};
}
export interface ISplitViewReactProps {
orientation: Orientation;

View File

@ -6,7 +6,7 @@
"types": "dist/esm/index.d.ts",
"scripts": {
"build": "gulp run",
"docs": "typedoc --excludeNotExported --excludePrivate true --mode file --out typedocs/ src/"
"docs": "typedoc"
},
"author": "",
"license": "ISC",

View File

@ -1,5 +1,5 @@
import { Orientation, Sizing } from "../splitview/splitview";
import { Target } from "../groupview/droptarget/droptarget";
import { Position } from "../groupview/droptarget/droptarget";
import { tail } from "../array";
import { LeafNode } from "./leafNode";
import { BranchNode } from "./branchNode";
@ -91,7 +91,7 @@ export function getGridLocation(element: HTMLElement): number[] {
export function getRelativeLocation(
rootOrientation: Orientation,
location: number[],
direction: Target
direction: Position
): number[] {
const orientation = getLocationOrientation(rootOrientation, location);
const directionOrientation = getDirectionOrientation(direction);
@ -99,20 +99,20 @@ export function getRelativeLocation(
if (orientation === directionOrientation) {
let [rest, index] = tail(location);
if (direction === Target.Right || direction === Target.Bottom) {
if (direction === Position.Right || direction === Position.Bottom) {
index += 1;
}
return [...rest, index];
} else {
const index =
direction === Target.Right || direction === Target.Bottom ? 1 : 0;
direction === Position.Right || direction === Position.Bottom ? 1 : 0;
return [...location, index];
}
}
export function getDirectionOrientation(direction: Target): Orientation {
return direction === Target.Top || direction === Target.Bottom
export function getDirectionOrientation(direction: Position): Orientation {
return direction === Position.Top || direction === Position.Bottom
? Orientation.VERTICAL
: Orientation.HORIZONTAL;
}
@ -193,9 +193,9 @@ export interface INodeDescriptor {
visible?: boolean;
}
export type IViewDeserializer = {
export interface IViewDeserializer {
fromJSON: (data: {}) => IGridView;
};
}
export class Gridview {
private _root: BranchNode;

View File

@ -11,12 +11,12 @@ export enum DragType {
EXTERNAL = "external_group_drag",
}
export type DragItem = {
export interface DragItem {
itemId: string;
groupId: string;
};
}
export type ExternalDragItem = PanelOptions;
export interface ExternalDragItem extends PanelOptions {}
export type DataObject = DragItem | ExternalDragItem;

View File

@ -20,18 +20,18 @@
transition-duration: 0.15s;
transition-timing-function: ease-out;
&.left {
&.left,
&.right {
width: 50%;
}
&.right {
left: 50%;
width: 50%;
transform: translate(100%, 0%);
}
&.bottom {
top: 50%;
height: 50%;
transform: translate(0%, 100%);
}
&.top {
&.top,
&.bottom {
height: 50%;
}
}

View File

@ -1,7 +1,7 @@
import { Emitter, Event } from "../../events";
import { DataTransferSingleton } from "./dataTransfer";
export enum Target {
export enum Position {
Top = "Top",
Left = "Left",
Bottom = "Bottom",
@ -9,10 +9,10 @@ export enum Target {
Center = "Center",
}
export type DroptargetEvent = {
target: Target;
export interface DroptargetEvent {
position: Position;
event: DragEvent;
};
}
const HAS_PROCESSED_KEY = "__drop_target_processed__";
@ -39,7 +39,7 @@ const toggleClassName = (
export class Droptarget {
private target: HTMLElement;
private overlay: HTMLElement;
private state: Target;
private state: Position | undefined;
private readonly _onDidChange = new Emitter<DroptargetEvent>();
readonly onDidChange: Event<DroptargetEvent> = this._onDidChange.event;
@ -106,7 +106,7 @@ export class Droptarget {
this.removeDropTarget();
if (!hasProcessed(event)) {
this._onDidChange.fire({ target: this.state, event });
this._onDidChange.fire({ position: this.state, event });
} else {
console.debug("[dragtarget] already processed");
}
@ -140,15 +140,15 @@ export class Droptarget {
toggleClassName(this.overlay, "bottom", isBottom);
if (isRight) {
this.state = Target.Right;
this.state = Position.Right;
} else if (isLeft) {
this.state = Target.Left;
this.state = Position.Left;
} else if (isTop) {
this.state = Target.Top;
this.state = Position.Top;
} else if (isBottom) {
this.state = Target.Bottom;
this.state = Position.Bottom;
} else {
this.state = Target.Center;
this.state = Position.Center;
}
};

View File

@ -1,11 +1,6 @@
import { DroptargetEvent } from "./droptarget/droptarget";
export enum TabChangedEventType {
CLICK,
}
export type TabChangedEvent = { type: TabChangedEventType };
export type TabDropEvent = {
export interface TabDropEvent {
event: DroptargetEvent;
index?: number;
};
}

View File

@ -1,8 +1,8 @@
import { IDisposable, CompositeDisposable, Disposable } from "../lifecycle";
import { ITabContainer, TabContainer } from "./tabs/tabContainer";
import { IContentContainer, ContentContainer } from "./content";
import { ITabContainer, TabContainer } from "./titlebar/tabContainer";
import { IContentContainer, ContentContainer } from "./panel/content/content";
import { IGridView } from "../gridview/gridview";
import { Target, Droptarget, DroptargetEvent } from "./droptarget/droptarget";
import { Position, Droptarget, DroptargetEvent } from "./droptarget/droptarget";
import { Event, Emitter, addDisposableListener } from "../events";
import { IGroupAccessor, Layout } from "../layout";
import { toggleClass } from "../dom";
@ -44,12 +44,12 @@ export interface IGroupItem {
body: { element: HTMLElement };
}
type GroupMoveEvent = {
interface GroupMoveEvent {
groupId: string;
itemId: string;
target: Target;
target: Position;
index?: number;
};
}
export interface GroupOptions {
panels: IPanel[];
@ -88,11 +88,11 @@ export interface IGroupview extends IDisposable, IGridView {
moveToPrevious(options?: { panel?: IPanel; suppressRoll?: boolean }): void;
}
export type GroupDropEvent = {
export interface GroupDropEvent {
event: DragEvent;
target: Target;
target: Position;
index?: number;
};
}
export class Groupview extends CompositeDisposable implements IGroupview {
private _element: HTMLElement;
@ -129,6 +129,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
set tabHeight(height: number) {
this.tabContainer.height = height;
this.layout(this._width, this._height);
}
get isActive() {
@ -290,7 +291,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
this.dropTarget.onDidChange((event) => {
// if we've center dropped on ourself then ignore
if (
event.target === Target.Center &&
event.position === Position.Center &&
this.tabContainer.hasActiveDragEvent
) {
return;
@ -531,18 +532,18 @@ export class Groupview extends CompositeDisposable implements IGroupview {
private handleDropEvent(event: DroptargetEvent, index?: number) {
if (isPanelTransferEvent(event.event)) {
this.handlePanelDropEvent(event.event, event.target, index);
this.handlePanelDropEvent(event.event, event.position, index);
return;
}
this._onDrop.fire({ event: event.event, target: event.target, index });
this._onDrop.fire({ event: event.event, target: event.position, index });
console.debug("[customDropEvent]");
}
private handlePanelDropEvent(
event: DragEvent,
target: Target,
target: Position,
index?: number
) {
const dataObject = extractData(event);
@ -550,10 +551,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
if (isTabDragEvent(dataObject)) {
const { groupId, itemId } = dataObject;
const isSameGroup = this.id === groupId;
if (isSameGroup) {
const index = this.tabContainer.indexOf(itemId);
if (index > -1 && index === this.panels.length - 1) {
console.debug("[tabs] dropped in empty space");
if (isSameGroup && !target) {
const oldIndex = this.tabContainer.indexOf(itemId);
if (oldIndex === index) {
console.debug("[tabs] drop indicates no change in position");
return;
}
}

View File

@ -4,15 +4,15 @@ import { ClosePanelResult } from "./parts";
import { IPanel } from "./types";
import { CompositeDisposable, IDisposable } from "../../lifecycle";
export type PanelStateChangeEvent = {
export interface PanelStateChangeEvent {
isPanelVisible: boolean;
isGroupActive: boolean;
};
}
export type PanelDimensionChangeEvent = {
export interface PanelDimensionChangeEvent {
width: number;
height: number;
};
}
export interface PanelApi extends IDisposable {
onDidPanelStateChange: Event<PanelStateChangeEvent>;

View File

@ -1,6 +1,6 @@
import { CompositeDisposable, IDisposable } from "../lifecycle";
import { Emitter, Event } from "../events";
import { trackFocus } from "../dom";
import { CompositeDisposable, IDisposable } from "../../../lifecycle";
import { Emitter, Event } from "../../../events";
import { trackFocus } from "../../../dom";
export interface IContentContainer extends IDisposable {
onDidFocus: Event<void>;

View File

@ -149,7 +149,11 @@ export class DefaultPanel extends CompositeDisposable implements IPanel {
}
public layout(width: number, height: number) {
this._onDidPanelDimensionsChange.fire({ width, height });
// thw height of the panel excluded the height of the title/tab
this._onDidPanelDimensionsChange.fire({
width,
height: height - (this.group?.tabHeight || 0),
});
}
public dispose() {

View File

@ -14,13 +14,13 @@ interface Methods extends IDisposable {
setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void;
}
export type WatermarkPartInitParameters = {
export interface WatermarkPartInitParameters {
accessor: IGroupAccessor;
};
}
export type PartInitParameters = {
export interface PartInitParameters extends PanelInitParameters {
api: PanelApi;
} & PanelInitParameters;
}
export interface PanelHeaderPart extends Methods {
id: string;

View File

@ -1,23 +1,31 @@
import { addDisposableListener, Emitter, Event } from "../../events";
import { Droptarget, DroptargetEvent } from "../droptarget/droptarget";
import { CompositeDisposable } from "../../lifecycle";
import { TabChangedEvent, TabDropEvent, TabChangedEventType } from "../events";
import { IGroupview } from "../groupview";
import { addDisposableListener, Emitter, Event } from "../../../events";
import { Droptarget, DroptargetEvent } from "../../droptarget/droptarget";
import { CompositeDisposable } from "../../../lifecycle";
import { IGroupview } from "../../groupview";
import {
DataTransferSingleton,
DATA_KEY,
DragType,
extractData,
} from "../droptarget/dataTransfer";
import { IGroupAccessor } from "../../layout";
import { toggleClass } from "../../dom";
} from "../../droptarget/dataTransfer";
// import { IGroupAccessor } from "../../layout";
import { toggleClass } from "../../../dom";
import { IGroupAccessor } from "../../../layout";
export enum TabInteractionKind {
CLICK = "CLICK",
CONTEXT_MENU = "CONTEXT_MEU",
}
export interface TabInteractionEvent {
kind: TabInteractionKind;
}
export interface ITab {
id: string;
element: HTMLElement;
hasActiveDragEvent: boolean;
setContent: (element: HTMLElement) => void;
onChanged: Event<TabChangedEvent>;
onChanged: Event<TabInteractionEvent>;
onDropped: Event<DroptargetEvent>;
setActive(isActive: boolean): void;
startDragEvent(): void;
@ -32,8 +40,8 @@ export class Tab extends CompositeDisposable implements ITab {
private droptarget: Droptarget;
private content: HTMLElement;
private readonly _onChanged = new Emitter<TabChangedEvent>();
readonly onChanged: Event<TabChangedEvent> = this._onChanged.event;
private readonly _onChanged = new Emitter<TabInteractionEvent>();
readonly onChanged: Event<TabInteractionEvent> = this._onChanged.event;
private readonly _onDropped = new Emitter<DroptargetEvent>();
readonly onDropped: Event<DroptargetEvent> = this._onDropped.event;
@ -72,7 +80,10 @@ export class Tab extends CompositeDisposable implements ITab {
if (ev.defaultPrevented) {
return;
}
this._onChanged.fire({ type: TabChangedEventType.CLICK });
this._onChanged.fire({ kind: TabInteractionKind.CLICK });
}),
addDisposableListener(this._element, "contextmenu", (ev) => {
this._onChanged.fire({ kind: TabInteractionKind.CONTEXT_MENU });
}),
addDisposableListener(this._element, "dragstart", (event) => {
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id };

View File

@ -5,18 +5,18 @@ import { PanelHeaderPart, PanelContentPart, ClosePanelResult } from "./parts";
// objects
export type PanelUpdateEvent = {
export interface PanelUpdateEvent {
params: { [key: string]: any };
};
}
// init parameters
export type PanelInitParameters = {
export interface PanelInitParameters {
title: string;
suppressClosable?: boolean;
params: { [index: string]: any };
state?: { [index: string]: any };
};
}
// constructors

View File

@ -1,8 +1,8 @@
import { IDisposable, CompositeDisposable } from "../../lifecycle";
import { addDisposableListener, Emitter, Event } from "../../events";
import { ITab, Tab } from "./tab";
import { ITab, Tab, TabInteractionKind } from "../panel/tab/tab";
import { removeClasses, addClasses, toggleClass } from "../../dom";
import { hasProcessed } from "../droptarget/droptarget";
import { hasProcessed, Position } from "../droptarget/droptarget";
import { TabDropEvent } from "../events";
import { IGroupview } from "../groupview";
@ -144,7 +144,8 @@ export class TabContainer extends CompositeDisposable implements ITabContainer {
}
this._onDropped.fire({
event: { event, target: undefined },
event: { event, position: Position.Center },
index: this.tabs.length - 1,
});
})
);
@ -195,7 +196,13 @@ export class TabContainer extends CompositeDisposable implements ITabContainer {
// TODO - dispose of resources
const disposables = CompositeDisposable.from(
tab.onChanged((event) => {
switch (event.kind) {
case TabInteractionKind.CLICK:
this.group.openPanel(panel);
break;
case TabInteractionKind.CONTEXT_MENU:
// TODO finish
}
}),
tab.onDropped((event) => {
this._onDropped.fire({ event, index: this.indexOf(tab) });

View File

@ -2,8 +2,8 @@ export * from "./splitview/splitview";
export * from "./splitview/paneview";
export * from "./gridview/gridview";
export * from "./groupview/groupview";
export * from "./groupview/content";
export * from "./groupview/tabs/tab";
export * from "./groupview/panel/content/content";
export * from "./groupview/panel/tab/tab";
export * from "./events";
export * from "./lifecycle";
export * from "./groupview/panel/panel";
@ -12,6 +12,7 @@ export * from "./react/react";
export * from "./groupview/panel/types";
export * from "./groupview/panel/parts";
export * from "./react/layout";
export * from "./react/splitview";
export * from "./react/reactContentPart";
export * from "./react/reactHeaderPart";

View File

@ -1,67 +1,17 @@
.groupview {
&.active-group {
> .title-container > .tab-container > .tab {
&.active-tab {
.default-tab {
background-color: var(--tab-background-visible);
color: var(--active-group-visible-panel-color);
}
.tab-action {
background-color: var(--active-group-visible-panel-color);
}
}
&.inactive-tab {
.default-tab {
background-color: var(--tab-background-hidden);
color: var(--active-group-hidden-panel-color);
}
.tab-action {
background-color: var(--active-group-hidden-panel-color);
}
}
}
}
&.inactive-group {
> .title-container > .tab-container > .tab {
&.active-tab {
.default-tab {
background-color: var(--tab-background-visible);
color: var(--inactive-group-visible-panel-color);
}
.tab-action {
background-color: var(--inactive-group-visible-panel-color);
}
}
&.inactive-tab {
.default-tab {
background-color: var(--tab-background-hidden);
color: var(--inactive-group-hidden-panel-color);
}
.tab-action {
background-color: var(--inactive-group-hidden-panel-color);
}
}
}
}
}
.tab {
&.dragging {
color: var(--active-group-visible-panel-color);
.tab-action {
background-color: var(--active-group-visible-panel-color);
}
}
&.active-tab > .default-tab {
background-color: var(--tab-background-visible);
.tab-action {
visibility: visible;
}
}
&.inactive-tab > .default-tab {
background-color: var(--tab-background-hidden);
.tab-action:not(.dirty) {
visibility: hidden;
}

View File

@ -9,3 +9,55 @@
position: absolute;
padding-left: 10px;
}
.groupview {
&.active-group {
> .title-container > .tab-container > .tab {
&.active-tab {
background-color: var(--active-tab-background-visible);
color: var(--active-group-visible-panel-color);
.tab-action {
background-color: var(--active-group-visible-panel-color);
}
}
&.inactive-tab {
background-color: var(--active-tab-background-hidden);
color: var(--active-group-hidden-panel-color);
.tab-action {
background-color: var(--active-group-hidden-panel-color);
}
}
}
}
&.inactive-group {
> .title-container > .tab-container > .tab {
&.active-tab {
background-color: var(--inactive-tab-background-visible);
color: var(--inactive-group-visible-panel-color);
.tab-action {
background-color: var(--inactive-group-visible-panel-color);
}
}
&.inactive-tab {
background-color: var(--inactive-tab-background-hidden);
color: var(--inactive-group-hidden-panel-color);
.tab-action {
background-color: var(--inactive-group-hidden-panel-color);
}
}
}
}
}
// when a tab is dragged we loss the above stylings because they are conditional on parent elements
// there we also set some stylings for the dragging event
.tab {
&.dragging {
background-color: var(--active-tab-background-visible);
color: var(--active-group-visible-panel-color);
}
}

View File

@ -1,5 +1,5 @@
import { Gridview, getRelativeLocation } from "../gridview/gridview";
import { Target } from "../groupview/droptarget/droptarget";
import { Position } from "../groupview/droptarget/droptarget";
import { getGridLocation } from "../gridview/gridview";
import { tail, sequenceEquals } from "../array";
import {
@ -45,10 +45,10 @@ import {
const nextGroupId = sequentialNumberGenerator();
const nextLayoutId = sequentialNumberGenerator();
export type PanelReference = {
export interface PanelReference {
update: (event: { params: { [key: string]: any } }) => void;
remove: () => void;
};
}
export interface Api {
layout(width: number, height: number): void;
@ -56,6 +56,7 @@ export interface Api {
setAutoResizeToFit(enabled: boolean): void;
resizeToFit(): void;
setTabHeight(height: number): void;
getTabHeight(): number;
size: number;
totalPanels: number;
// lifecycle
@ -90,7 +91,7 @@ export interface IGroupAccessor {
referenceGroup: IGroupview,
groupId: string,
itemId: string,
target: Target,
target: Position,
index?: number
): void;
doSetGroupActive: (group: IGroupview) => void;
@ -399,11 +400,16 @@ export class Layout extends CompositeDisposable implements ILayout {
}
public setTabHeight(height: number) {
this.options.tabHeight = height;
this.groups.forEach((value) => {
value.value.tabHeight = height;
});
}
public getTabHeight() {
return this.options.tabHeight;
}
public setAutoResizeToFit(enabled: boolean) {
if (this.resizeTimer) {
clearInterval(this.resizeTimer);
@ -435,7 +441,7 @@ export class Layout extends CompositeDisposable implements ILayout {
const referenceGroup = this.findGroup(referencePanel);
const target = this.toTarget(options.position.direction);
if (target === Target.Center) {
if (target === Position.Center) {
referenceGroup.openPanel(panel);
} else {
const location = getGridLocation(referenceGroup.element);
@ -597,13 +603,13 @@ export class Layout extends CompositeDisposable implements ILayout {
referenceGroup: IGroupview,
groupId: string,
itemId: string,
target: Target,
target: Position,
index?: number
) {
const sourceGroup = groupId ? this.groups.get(groupId).value : undefined;
switch (target) {
case Target.Center:
case Position.Center:
case undefined:
const groupItem =
sourceGroup?.removePanel(itemId) || this.panels.get(itemId).value;
@ -699,7 +705,7 @@ export class Layout extends CompositeDisposable implements ILayout {
}
this.moveGroup(
group,
panel?.group.id,
panel?.group?.id,
panel.id,
event.target,
event.index
@ -774,16 +780,16 @@ export class Layout extends CompositeDisposable implements ILayout {
private toTarget(direction: "left" | "right" | "above" | "below" | "within") {
switch (direction) {
case "left":
return Target.Left;
return Position.Left;
case "right":
return Target.Right;
return Position.Right;
case "above":
return Target.Top;
return Position.Top;
case "below":
return Target.Bottom;
return Position.Bottom;
case "within":
default:
return Target.Center;
return Position.Center;
}
}

View File

@ -1,4 +1,5 @@
import { IGroupview } from "../groupview/groupview";
import { PanelApi } from "../groupview/panel/api";
import {
PanelContentPart,
PanelContentPartConstructor,
@ -6,11 +7,20 @@ import {
PanelHeaderPartConstructor,
WatermarkConstructor,
} from "../groupview/panel/parts";
import { IPanel } from "../groupview/panel/types";
import { Api } from "./layout";
export type FrameworkPanelWrapper = {
export interface FrameworkPanelWrapper {
createContentWrapper: (id: string, component: any) => PanelContentPart;
createTabWrapper: (id: string, component: any) => PanelHeaderPart;
};
}
export interface TabContextMenuEvent {
event: MouseEvent;
api: Api;
panelApi: PanelApi;
panel: IPanel;
}
export interface LayoutOptions {
tabComponents?: {
@ -31,6 +41,7 @@ export interface LayoutOptions {
tabHeight?: number;
debug?: boolean;
enableExternalDragEvents?: boolean;
onTabContextMenu?: (event: TabContextMenuEvent) => void;
}
export interface PanelOptions {

View File

@ -6,9 +6,9 @@ import { ReactPanelHeaderPart } from "./reactHeaderPart";
import { IPanelProps } from "./react";
import { ReactPanelDeserialzier } from "./deserializer";
export type OnReadyEvent = {
export interface OnReadyEvent {
api: Api;
};
}
export interface ReactLayout {
addPortal: (portal: React.ReactPortal) => IDisposable;

View File

@ -0,0 +1,57 @@
import { Emitter } from "../events";
import { IView } from "../splitview/splitview";
import { ReactLayout } from "./layout";
import { ReactPart } from "./react";
export class ReactView implements IView {
private _element: HTMLElement;
private part: ReactPart;
private _onDidChange: Emitter<number | undefined> = new Emitter<
number | undefined
>();
public onDidChange = this._onDidChange.event;
get element() {
return this._element;
}
get minimumSize() {
return 100;
}
// get snapSize() {
// return 100;
// }
get maximumSize() {
return Number.MAX_SAFE_INTEGER;
}
constructor(
public readonly id: string,
private readonly component: React.FunctionComponent<{}>,
private readonly parent: ReactLayout
) {
if (!this.component) {
throw new Error("React.FunctionalComponent cannot be undefined");
}
this._element = document.createElement("div");
}
layout(size: number, orthogonalSize: number) {}
init(parameters: { params: any }): void {
this.part = new ReactPart(
this.element,
{} as any,
this.parent.addPortal,
this.component,
parameters.params
);
}
update(params: {}) {
this.part.update(params);
}
}

View File

@ -0,0 +1,95 @@
import * as React from "react";
import { Orientation, SplitView } from "../splitview/splitview";
import { ReactView } from "./reactView";
export interface SplitviewFacade {
addFromComponent(options: { id: string; component: string }): void;
layout(size: number, orthogonalSize: number): void;
}
export interface SplitviewReadyEvent {
api: SplitviewFacade;
}
export interface ISplitviewComponentProps {
orientation: Orientation;
onReady?: (event: SplitviewReadyEvent) => void;
components: { [index: string]: React.FunctionComponent<{}> };
}
export const SplitViewComponent = (props: ISplitviewComponentProps) => {
const domReference = React.useRef<HTMLDivElement>();
const splitview = React.useRef<SplitView>();
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]);
const addPortal = React.useCallback((p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]);
return {
dispose: () => {
setPortals((portals) => portals.filter((portal) => portal !== p));
},
};
}, []);
React.useEffect(() => {
splitview.current = new SplitView(domReference.current, {
orientation: props.orientation,
});
const createViewWrapper = (
id: string,
component: React.FunctionComponent<{}>
) => {
return new ReactView(id, component, { addPortal });
};
const facade: SplitviewFacade = {
addFromComponent: (options) => {
const component = props.components[options.component];
const view = createViewWrapper(options.id, component);
splitview.current.addView(view, { type: "distribute" });
view.init({ params: {} });
return {
dispose: () => {
//
},
};
},
layout: (width, height) => {
const [size, orthogonalSize] =
props.orientation === Orientation.HORIZONTAL
? [width, height]
: [height, width];
splitview.current.layout(size, orthogonalSize);
},
};
const { width, height } = domReference.current.getBoundingClientRect();
const [size, orthogonalSize] =
props.orientation === Orientation.HORIZONTAL
? [width, height]
: [height, width];
splitview.current.layout(size, orthogonalSize);
if (props.onReady) {
props.onReady({ api: facade });
}
return () => {
splitview.current.dispose();
};
}, []);
return (
<div
style={{
height: "100%",
width: "100%",
}}
ref={domReference}
>
{portals}
</div>
);
};

View File

@ -4,11 +4,13 @@
--title-bar-background-color: #252526;
--title-bar-scroll-bar-color: #888;
//
--tab-background-visible: #1e1e1e;
--tab-background-hidden: #2d2d2d;
--active-tab-background-visible: #1e1e1e;
--active-tab-background-hidden: #2d2d2d;
--inactive-tab-background-visible: #1e1e1e;
--inactive-tab-background-hidden: #2d2d2d;
--tab-divider-color: #1e1e1e;
//
--drag-over-background-color: rgba(255, 0, 0, 0.5);
--drag-over-background-color: rgba(83, 89, 93, 0.5);
//
--active-group-visible-panel-color: white;
--active-group-hidden-panel-color: #969696;
@ -20,3 +22,20 @@
//
--splitview-divider-color: rgb(68, 68, 68);
}
.visual-studio-theme {
--active-tab-background-visible: dodgerblue;
.groupview {
&.active-group {
> .title-container {
border-bottom: 2px solid var(--active-tab-background-visible);
}
}
&.inactive-group {
> .title-container {
border-bottom: 2px solid var(--inactive-tab-background-visible);
}
}
}
}

View File

@ -0,0 +1,11 @@
{
"out": "typedocs",
"mode": "file",
"inputFiles": ["./src"],
"exclude": ["**/_test/**/*.*", "**/index.ts"],
"ignoreCompilerErrors": true,
"disableOutputCheck": true,
"excludeExternals": true,
"excludePrivate": true,
"excludeNotExported": true
}