From 62b2e30ed6c76a04884682ebd86f69db1378b5d7 Mon Sep 17 00:00:00 2001 From: mathuo Date: Mon, 31 Aug 2020 21:43:37 +0100 Subject: [PATCH] code --- .gitignore | 2 +- .vscode/settings.json | 1 + package-lock.json | 165 +++++++++++++++++- package.json | 1 + .../src/layout-grid/editorPanel.tsx | 33 ++++ .../src/layout-grid/reactgrid.tsx | 34 +--- .../src/layout-grid/splitPanel.tsx | 64 +++++-- packages/splitview/package.json | 1 + packages/splitview/src/groupview/groupview.ts | 68 ++++---- packages/splitview/src/groupview/panel/api.ts | 53 ++---- .../splitview/src/groupview/panel/panel.ts | 13 +- .../splitview/src/groupview/panel/parts.ts | 23 ++- .../splitview/src/groupview/panel/types.ts | 19 +- .../src/groupview/titlebar/tabContainer.ts | 74 +++++--- packages/splitview/src/index.ts | 2 +- .../splitview/src/layout/componentFactory.ts | 9 +- packages/splitview/src/layout/deserializer.ts | 8 +- packages/splitview/src/layout/layout.ts | 98 ++++++----- packages/splitview/src/layout/options.ts | 13 +- packages/splitview/src/panel/api.ts | 71 ++++++++ packages/splitview/src/panel/types.ts | 14 ++ .../src/{splitview => paneview}/paneview.scss | 0 .../src/{splitview => paneview}/paneview.ts | 2 +- packages/splitview/src/react/deserializer.ts | 10 +- packages/splitview/src/react/layout.tsx | 29 +-- packages/splitview/src/react/react.tsx | 7 +- .../splitview/src/react/reactComponentView.ts | 102 +++++++++++ packages/splitview/src/react/reactView.ts | 57 ------ packages/splitview/src/react/splitview.tsx | 74 ++++---- .../src/splitview/componentSplitview.ts | 128 ++++++++++++++ packages/splitview/src/splitview/options.ts | 58 ++++++ packages/splitview/src/splitview/splitview.ts | 57 +++--- packages/splitview/src/types.ts | 7 + scripts/build.js | 9 +- 34 files changed, 924 insertions(+), 382 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 packages/splitview-demo/src/layout-grid/editorPanel.tsx create mode 100644 packages/splitview/src/panel/api.ts create mode 100644 packages/splitview/src/panel/types.ts rename packages/splitview/src/{splitview => paneview}/paneview.scss (100%) rename packages/splitview/src/{splitview => paneview}/paneview.ts (99%) create mode 100644 packages/splitview/src/react/reactComponentView.ts delete mode 100644 packages/splitview/src/react/reactView.ts create mode 100644 packages/splitview/src/splitview/componentSplitview.ts create mode 100644 packages/splitview/src/splitview/options.ts create mode 100644 packages/splitview/src/types.ts diff --git a/.gitignore b/.gitignore index 5ee85185a..59c40ebac 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules/ dist/ typedocs/ .DS_Store -lerna-debug.log \ No newline at end of file +*-debug.log \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 99cddca33..ef80f3e14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "splitview", + "name": "splitview-root", "version": "1.0.0", "lockfileVersion": 1, "requires": true, @@ -611,6 +611,57 @@ } } }, + "@gulp-sourcemaps/identity-map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", + "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "dev": true, + "requires": { + "acorn": "^5.0.3", + "css": "^2.2.1", + "normalize-path": "^2.1.1", + "source-map": "^0.6.0", + "through2": "^2.0.3" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -5788,6 +5839,28 @@ "ms": "^2.1.1" } }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "debuglog": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", @@ -6467,6 +6540,16 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "eventemitter3": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", @@ -8809,6 +8892,39 @@ } } }, + "gulp-sourcemaps": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", + "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.X", + "@gulp-sourcemaps/map-sources": "1.X", + "acorn": "5.X", + "convert-source-map": "1.X", + "css": "2.X", + "debug-fabulous": "1.X", + "detect-newline": "2.X", + "graceful-fs": "4.X", + "source-map": "~0.6.0", + "strip-bom-string": "1.X", + "through2": "2.X" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + } + } + }, "gulp-typescript": { "version": "6.0.0-alpha.1", "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", @@ -9916,6 +10032,12 @@ "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", "dev": true }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "is-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", @@ -11710,6 +11832,15 @@ "yallist": "^2.1.2" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, "macos-release": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz", @@ -11967,6 +12098,22 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -15658,6 +15805,12 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -15962,6 +16115,16 @@ "setimmediate": "^1.0.4" } }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 1e07c6e2e..ce2a1e6d5 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "gulp-concat": "^2.6.1", "gulp-header": "^2.0.9", "gulp-sass": "^4.1.0", + "gulp-sourcemaps": "^2.6.5", "gulp-typescript": "^6.0.0-alpha.1", "jest": "^26.1.0", "jsdom": "^16.2.2", diff --git a/packages/splitview-demo/src/layout-grid/editorPanel.tsx b/packages/splitview-demo/src/layout-grid/editorPanel.tsx new file mode 100644 index 000000000..dd06ab6c6 --- /dev/null +++ b/packages/splitview-demo/src/layout-grid/editorPanel.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; +import { Api, IPanelProps } from "splitview"; + +export const Editor = (props: IPanelProps & { layoutApi: Api }) => { + const [tabHeight, setTabHeight] = React.useState(0); + + React.useEffect(() => { + if (props.layoutApi) { + setTabHeight(props.layoutApi.getTabHeight()); + } + }, [props.layoutApi]); + + const onTabHeightChange = (event: React.ChangeEvent) => { + const value = Number(event.target.value); + if (!Number.isNaN(value)) { + setTabHeight(value); + } + }; + + const onClick = () => { + props.layoutApi.setTabHeight(tabHeight); + }; + + return ( +
+ +
+ ); +}; diff --git a/packages/splitview-demo/src/layout-grid/reactgrid.tsx b/packages/splitview-demo/src/layout-grid/reactgrid.tsx index 59e396e12..68a63c430 100644 --- a/packages/splitview-demo/src/layout-grid/reactgrid.tsx +++ b/packages/splitview-demo/src/layout-grid/reactgrid.tsx @@ -10,39 +10,9 @@ import { GroupChangeKind, } from "splitview"; import { CustomTab } from "./customTab"; +import { Editor } from "./editorPanel"; import { SplitPanel } from "./splitPanel"; -const Editor = (props: IPanelProps & { layoutApi: Api }) => { - const [tabHeight, setTabHeight] = React.useState(0); - - React.useEffect(() => { - if (props.layoutApi) { - setTabHeight(props.layoutApi.getTabHeight()); - } - }, [props.layoutApi]); - - const onTabHeightChange = (event: React.ChangeEvent) => { - const value = Number(event.target.value); - if (!Number.isNaN(value)) { - setTabHeight(value); - } - }; - - const onClick = () => { - props.layoutApi.setTabHeight(tabHeight); - }; - - return ( -
- -
- ); -}; - const components = { inner_component: (props: IPanelProps) => { const _api = React.useRef(); @@ -51,7 +21,7 @@ const components = { const onReady = (event: OnReadyEvent) => { _api.current = event.api; - const layout = props.api.getState()["layout"]; + const layout = props.api.getStateKey("layout"); if (layout) { event.api.deserialize(layout); } else { diff --git a/packages/splitview-demo/src/layout-grid/splitPanel.tsx b/packages/splitview-demo/src/layout-grid/splitPanel.tsx index 55948381c..844ae624c 100644 --- a/packages/splitview-demo/src/layout-grid/splitPanel.tsx +++ b/packages/splitview-demo/src/layout-grid/splitPanel.tsx @@ -1,6 +1,8 @@ import * as React from "react"; import { + CompositeDisposable, IPanelProps, + ISplitviewPanelProps, Orientation, SplitviewFacade, SplitviewReadyEvent, @@ -8,7 +10,20 @@ import { import { SplitViewComponent } from "splitview"; const components = { - default1: (props) => { + default1: (props: ISplitviewPanelProps) => { + React.useEffect(() => { + const disposable = new CompositeDisposable(); + disposable.addDisposables( + props.api.onDidPanelDimensionChange((event) => { + // + }) + ); + + return () => { + disposable.dispose(); + }; + }, []); + return
hiya
; }, }; @@ -18,26 +33,47 @@ export const SplitPanel = (props: IPanelProps) => { 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); + api.current?.layout(event.width, event.height - 20); + }); + + api.current.onChange((event) => { + props.api.setState("sview_layout", api.current.toJSON()); }); }, []); const onReady = (event: SplitviewReadyEvent) => { - event.api.addFromComponent({ id: "1", component: "default1" }); - event.api.addFromComponent({ id: "2", component: "default1" }); + const existingLayout = props.api.getStateKey("sview_layout"); + + if (existingLayout) { + event.api.deserialize(existingLayout); + } else { + event.api.addFromComponent({ id: "1", component: "default1" }); + event.api.addFromComponent({ id: "2", component: "default1" }); + } api.current = event.api; }; + const onSave = () => { + props.api.setState("sview_layout", api.current.toJSON()); + }; + return ( - +
+
+ +
+ +
); }; diff --git a/packages/splitview/package.json b/packages/splitview/package.json index f58f468c5..384434f86 100644 --- a/packages/splitview/package.json +++ b/packages/splitview/package.json @@ -4,6 +4,7 @@ "description": "", "main": "dist/esm/index.js", "types": "dist/esm/index.d.ts", + "module": "dist/esm/index.js", "scripts": { "build": "gulp run", "docs": "typedoc" diff --git a/packages/splitview/src/groupview/groupview.ts b/packages/splitview/src/groupview/groupview.ts index dae75bd01..bfba463c7 100644 --- a/packages/splitview/src/groupview/groupview.ts +++ b/packages/splitview/src/groupview/groupview.ts @@ -7,7 +7,7 @@ import { Event, Emitter, addDisposableListener } from "../events"; import { IGroupAccessor, Layout } from "../layout"; import { toggleClass } from "../dom"; import { ClosePanelResult, WatermarkPart } from "./panel/parts"; -import { IPanel } from "./panel/types"; +import { IGroupPanel } from "./panel/types"; import { timeoutPromise } from "../async"; import { extractData, @@ -52,40 +52,43 @@ interface GroupMoveEvent { } export interface GroupOptions { - panels: IPanel[]; - activePanel?: IPanel; + panels: IGroupPanel[]; + activePanel?: IGroupPanel; } export interface GroupChangeEvent { kind: GroupChangeKind; - panel?: IPanel; + panel?: IGroupPanel; } export interface IGroupview extends IDisposable, IGridView { id: string; size: number; - panels: IPanel[]; + panels: IGroupPanel[]; tabHeight: number; setActive: (isActive: boolean) => void; // state - isPanelActive: (panel: IPanel) => boolean; + isPanelActive: (panel: IGroupPanel) => boolean; isActive: boolean; - activePanel: IPanel; - indexOf(panel: IPanel): number; + activePanel: IGroupPanel; + indexOf(panel: IGroupPanel): number; // panel lifecycle - openPanel(panel: IPanel, index?: number): void; - closePanel(panel: IPanel): Promise; + openPanel(panel: IGroupPanel, index?: number): void; + closePanel(panel: IGroupPanel): Promise; closeAllPanels(): Promise; - containsPanel(panel: IPanel): boolean; - removePanel: (panelOrId: IPanel | string) => IPanel; + containsPanel(panel: IGroupPanel): boolean; + removePanel: (panelOrId: IGroupPanel | string) => IGroupPanel; // events onDidGroupChange: Event<{ kind: GroupChangeKind }>; onMove: Event; // - startActiveDrag(panel: IPanel): IDisposable; + startActiveDrag(panel: IGroupPanel): IDisposable; // - moveToNext(options?: { panel?: IPanel; suppressRoll?: boolean }): void; - moveToPrevious(options?: { panel?: IPanel; suppressRoll?: boolean }): void; + moveToNext(options?: { panel?: IGroupPanel; suppressRoll?: boolean }): void; + moveToPrevious(options?: { + panel?: IGroupPanel; + suppressRoll?: boolean; + }): void; } export interface GroupDropEvent { @@ -100,14 +103,14 @@ export class Groupview extends CompositeDisposable implements IGroupview { private tabContainer: ITabContainer; private contentContainer: IContentContainer; private _active: boolean; - private _activePanel: IPanel; + private _activePanel: IGroupPanel; private dropTarget: Droptarget; private watermark: WatermarkPart; private _width: number; private _height: number; - private _panels: IPanel[] = []; + private _panels: IGroupPanel[] = []; private readonly _onMove = new Emitter(); readonly onMove: Event = this._onMove.event; @@ -168,7 +171,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { return Number.MAX_SAFE_INTEGER; } - public indexOf(panel: IPanel) { + public indexOf(panel: IGroupPanel) { return this.tabContainer.indexOf(panel.id); } @@ -179,7 +182,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { }; } - public startActiveDrag(panel: IPanel): IDisposable { + public startActiveDrag(panel: IGroupPanel): IDisposable { const index = this.tabContainer.indexOf(panel.id); if (index > -1) { const tab = this.tabContainer.at(index); @@ -193,7 +196,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { return Disposable.NONE; } - public moveToNext(options?: { panel?: IPanel; suppressRoll?: boolean }) { + public moveToNext(options?: { panel?: IGroupPanel; suppressRoll?: boolean }) { if (!options) { options = {}; } @@ -218,7 +221,10 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.openPanel(this.panels[normalizedIndex]); } - public moveToPrevious(options?: { panel?: IPanel; suppressRoll?: boolean }) { + public moveToPrevious(options?: { + panel?: IGroupPanel; + suppressRoll?: boolean; + }) { if (!options) { options = {}; } @@ -243,7 +249,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.openPanel(this.panels[normalizedIndex]); } - public containsPanel(panel: IPanel) { + public containsPanel(panel: IGroupPanel) { return this.panels.includes(panel); } @@ -313,7 +319,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.updateContainer(); } - public openPanel(panel: IPanel, index: number = this.panels.length) { + public openPanel(panel: IGroupPanel, index: number = this.panels.length) { if (this._activePanel === panel) { this.accessor.doSetGroupActive(this); return; @@ -330,7 +336,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.updateContainer(); } - public removePanel(groupItemOrId: IPanel | string): IPanel { + public removePanel(groupItemOrId: IGroupPanel | string): IGroupPanel { const id = typeof groupItemOrId === "string" ? groupItemOrId : groupItemOrId.id; @@ -386,7 +392,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { return true; } - public closePanel = async (panel: IPanel) => { + public closePanel = async (panel: IGroupPanel) => { if (panel.close && (await panel.close()) === ClosePanelResult.DONT_CLOSE) { return false; } @@ -395,7 +401,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { return true; }; - private doClose(panel: IPanel) { + private doClose(panel: IGroupPanel) { this._removePanel(panel); (this.accessor as Layout).unregisterPanel(panel); @@ -407,7 +413,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { } } - public isPanelActive(panel: IPanel) { + public isPanelActive(panel: IGroupPanel) { return this._activePanel === panel; } @@ -447,7 +453,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { } } - private _removePanel(panel: IPanel) { + private _removePanel(panel: IGroupPanel) { const index = this._panels.indexOf(panel); const isActivePanel = this._activePanel === panel; @@ -467,7 +473,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { return panel; } - private doRemovePanel(panel: IPanel) { + private doRemovePanel(panel: IGroupPanel) { const index = this.panels.indexOf(panel); if (this._activePanel === panel) { @@ -480,7 +486,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { this._onDidGroupChange.fire({ kind: GroupChangeKind.REMOVE_PANEL, panel }); } - private doAddPanel(panel: IPanel, index: number) { + private doAddPanel(panel: IGroupPanel, index: number) { const existingPanel = this._panels.indexOf(panel); const hasExistingPabel = existingPanel > -1; @@ -494,7 +500,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { this._onDidGroupChange.fire({ kind: GroupChangeKind.ADD_PANEL }); } - private doSetActivePanel(panel: IPanel) { + private doSetActivePanel(panel: IGroupPanel) { this._activePanel = panel; this.tabContainer.setActivePanel(panel); panel.layout(this._width, this._height); diff --git a/packages/splitview/src/groupview/panel/api.ts b/packages/splitview/src/groupview/panel/api.ts index 65563508e..998bee3f2 100644 --- a/packages/splitview/src/groupview/panel/api.ts +++ b/packages/splitview/src/groupview/panel/api.ts @@ -1,53 +1,39 @@ import { IGroupview } from "../groupview"; -import { Event, Emitter } from "../../events"; +import { Event } from "../../events"; import { ClosePanelResult } from "./parts"; -import { IPanel } from "./types"; -import { CompositeDisposable, IDisposable } from "../../lifecycle"; +import { IGroupPanel } from "./types"; +import { + BasePanelApi, + IBasePanelApi, + PanelDimensionChangeEvent, +} from "../../panel/api"; export interface PanelStateChangeEvent { isPanelVisible: boolean; isGroupActive: boolean; } -export interface PanelDimensionChangeEvent { - width: number; - height: number; -} - -export interface PanelApi extends IDisposable { +export interface PanelApi extends IBasePanelApi { onDidPanelStateChange: Event; - onDidPanelDimensionChange: Event; isPanelVisible: boolean; isGroupActive: boolean; group: IGroupview; close: () => Promise; setClosePanelHook(callback: () => Promise): void; canClose: () => Promise; - setState(key: string, value: any); - setState(state: { [index: string]: any }); - getState: () => { [index: string]: any }; - onDidStateChange: Event; onDidDirtyChange: Event; } -export class PanelApiImpl extends CompositeDisposable implements PanelApi { +export class PanelApiImpl extends BasePanelApi implements PanelApi { private _isPanelVisible: boolean; private _isGroupActive: boolean; private _group: IGroupview; private _closePanelCallback: () => Promise; - private _state: { [index: string]: any } = {}; - - private readonly _onDidStateChange = new Emitter(); - readonly onDidStateChange: Event = this._onDidStateChange.event; get onDidPanelStateChange() { return this._event; } - get onDidPanelDimensionChange() { - return this._dimensionEvent; - } - get onDidDirtyChange() { return this._dirtyEvent; } @@ -74,12 +60,12 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { constructor( private _event: Event, - private _dimensionEvent: Event, + _dimensionEvent: Event, private _dirtyEvent: Event, - private panel: IPanel, + private panel: IGroupPanel, group: IGroupview ) { - super(); + super(_dimensionEvent); this._group = group; this.addDisposables( @@ -90,19 +76,6 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { ); } - public setState(key: string | { [index: string]: any }, value?: any) { - if (typeof key === "object") { - this._state = key; - } else { - this._state[key] = value; - } - this._onDidStateChange.fire(undefined); - } - - public getState(): { [index: string]: any } { - return this._state; - } - public close() { return this.group.closePanel(this.panel); } @@ -113,7 +86,5 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { public dispose() { super.dispose(); - - this._onDidStateChange.dispose(); } } diff --git a/packages/splitview/src/groupview/panel/panel.ts b/packages/splitview/src/groupview/panel/panel.ts index a0214376b..b9fe22bc5 100644 --- a/packages/splitview/src/groupview/panel/panel.ts +++ b/packages/splitview/src/groupview/panel/panel.ts @@ -1,16 +1,13 @@ -import { IPanel, PanelInitParameters, PanelUpdateEvent } from "./types"; -import { - PanelApiImpl, - PanelStateChangeEvent, - PanelDimensionChangeEvent, - PanelApi, -} from "./api"; +import { IGroupPanel, PanelInitParameters } from "./types"; +import { PanelApiImpl, PanelStateChangeEvent, PanelApi } from "./api"; import { Emitter, Event } from "../../events"; import { IGroupview, GroupChangeKind } from "../groupview"; import { MutableDisposable, CompositeDisposable } from "../../lifecycle"; import { PanelContentPart, PanelHeaderPart, ClosePanelResult } from "./parts"; +import { PanelDimensionChangeEvent } from "../../panel/api"; +import { PanelUpdateEvent } from "../../panel/types"; -export class DefaultPanel extends CompositeDisposable implements IPanel { +export class DefaultPanel extends CompositeDisposable implements IGroupPanel { private readonly mutableDisposable = new MutableDisposable(); private readonly _onDidPanelStateChange = new Emitter({ emitLastValue: true, diff --git a/packages/splitview/src/groupview/panel/parts.ts b/packages/splitview/src/groupview/panel/parts.ts index f52bcf778..edadb2aaa 100644 --- a/packages/splitview/src/groupview/panel/parts.ts +++ b/packages/splitview/src/groupview/panel/parts.ts @@ -3,13 +3,14 @@ import { IGroupview } from "../groupview"; import { IGroupAccessor } from "../../layout"; import { PanelApi } from "./api"; import { PanelInitParameters } from "./types"; +import { Constructor } from "../../types"; export enum ClosePanelResult { CLOSE = "CLOSE", DONT_CLOSE = "DONT_CLOSE", } -interface Methods extends IDisposable { +interface BasePart extends IDisposable { init?(params: PartInitParameters): void; setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void; } @@ -22,14 +23,14 @@ export interface PartInitParameters extends PanelInitParameters { api: PanelApi; } -export interface PanelHeaderPart extends Methods { +export interface PanelHeaderPart extends BasePart { id: string; element: HTMLElement; layout?(height: string): void; toJSON(): {}; } -export interface PanelContentPart extends Methods { +export interface PanelContentPart extends BasePart { id: string; element: HTMLElement; layout?(width: number, height: number): void; @@ -46,13 +47,11 @@ export interface WatermarkPart extends IDisposable { element: HTMLElement; } -export interface PanelHeaderPartConstructor { - new (): PanelHeaderPart; -} -export interface PanelContentPartConstructor { - new (): PanelContentPart; -} +// constructors -export interface WatermarkConstructor { - new (): WatermarkPart; -} +export interface PanelHeaderPartConstructor + extends Constructor {} +export interface PanelContentPartConstructor + extends Constructor {} + +export interface WatermarkConstructor extends Constructor {} diff --git a/packages/splitview/src/groupview/panel/types.ts b/packages/splitview/src/groupview/panel/types.ts index 00574d5bd..7442e8f09 100644 --- a/packages/splitview/src/groupview/panel/types.ts +++ b/packages/splitview/src/groupview/panel/types.ts @@ -2,31 +2,20 @@ import { IGroupview } from "../groupview"; import { IDisposable, ISerializable } from "../../lifecycle"; import { Event } from "../../events"; import { PanelHeaderPart, PanelContentPart, ClosePanelResult } from "./parts"; - -// objects - -export interface PanelUpdateEvent { - params: { [key: string]: any }; -} +import { InitParameters, IPanel } from "../../panel/types"; // init parameters -export interface PanelInitParameters { +export interface PanelInitParameters extends InitParameters { title: string; suppressClosable?: boolean; - params: { [index: string]: any }; - state?: { [index: string]: any }; } // constructors -export interface PanelConstructor { - new (): IPanel; -} - // panel -export interface IPanel extends IDisposable, ISerializable { +export interface IGroupPanel extends IDisposable, ISerializable, IPanel { id: string; header: PanelHeaderPart; content: PanelContentPart; @@ -36,8 +25,6 @@ export interface IPanel extends IDisposable, ISerializable { setVisible(isGroupActive: boolean, group: IGroupview): void; setDirty(isDirty: boolean): void; close?(): Promise; - layout?(width: number, height: number): void; init?(params: PanelInitParameters & { [index: string]: string }): void; - update?(event: PanelUpdateEvent): void; onDidStateChange: Event; } diff --git a/packages/splitview/src/groupview/titlebar/tabContainer.ts b/packages/splitview/src/groupview/titlebar/tabContainer.ts index 380ebf5b3..c5a5f39f0 100644 --- a/packages/splitview/src/groupview/titlebar/tabContainer.ts +++ b/packages/splitview/src/groupview/titlebar/tabContainer.ts @@ -1,4 +1,8 @@ -import { IDisposable, CompositeDisposable } from "../../lifecycle"; +import { + IDisposable, + CompositeDisposable, + IValueDisposable, +} from "../../lifecycle"; import { addDisposableListener, Emitter, Event } from "../../events"; import { ITab, Tab, TabInteractionKind } from "../panel/tab/tab"; import { removeClasses, addClasses, toggleClass } from "../../dom"; @@ -9,7 +13,7 @@ import { IGroupview } from "../groupview"; import { IGroupAccessor } from "../../layout"; import { last } from "../../array"; import { DataTransferSingleton } from "../droptarget/dataTransfer"; -import { IPanel } from "../panel/types"; +import { IGroupPanel } from "../panel/types"; export interface ITabContainer extends IDisposable { element: HTMLElement; @@ -21,10 +25,10 @@ export interface ITabContainer extends IDisposable { at: (index: number) => ITab; onDropEvent: Event; setActive: (isGroupActive: boolean) => void; - setActivePanel: (panel: IPanel) => void; + setActivePanel: (panel: IGroupPanel) => void; isActive: (tab: ITab) => boolean; - closePanel: (panel: IPanel) => void; - openPanel: (panel: IPanel, index?: number) => void; + closePanel: (panel: IGroupPanel) => void; + openPanel: (panel: IGroupPanel, index?: number) => void; } export class TabContainer extends CompositeDisposable implements ITabContainer { @@ -32,10 +36,10 @@ export class TabContainer extends CompositeDisposable implements ITabContainer { private _element: HTMLElement; private actionContainer: HTMLElement; - private tabs: ITab[] = []; + private tabs: IValueDisposable[] = []; private selectedIndex: number = -1; private active: boolean; - private activePanel: IPanel; + private activePanel: IGroupPanel; private _visible: boolean = true; private _height: number; @@ -67,20 +71,22 @@ export class TabContainer extends CompositeDisposable implements ITabContainer { } public isActive(tab: ITab) { - return this.selectedIndex > -1 && this.tabs[this.selectedIndex] === tab; + return ( + this.selectedIndex > -1 && this.tabs[this.selectedIndex].value === tab + ); } public get hasActiveDragEvent() { - return !!this.tabs.find((tab) => tab.hasActiveDragEvent); + return !!this.tabs.find((tab) => tab.value.hasActiveDragEvent); } public at(index: number) { - return this.tabs[index]; + return this.tabs[index]?.value; } public indexOf(tabOrId: ITab) { const id = typeof tabOrId === "string" ? tabOrId : tabOrId.id; - return this.tabs.findIndex((tab) => tab.id === id); + return this.tabs.findIndex((tab) => tab.value.id === id); } constructor(private accessor: IGroupAccessor, private group: IGroupview) { @@ -111,7 +117,7 @@ export class TabContainer extends CompositeDisposable implements ITabContainer { console.debug("[tabs] invalid drop event"); return; } - if (!last(this.tabs).hasActiveDragEvent) { + if (!last(this.tabs).value.hasActiveDragEvent) { addClasses(this.tabContainer, "drag-over-target"); } }), @@ -132,10 +138,11 @@ export class TabContainer extends CompositeDisposable implements ITabContainer { } removeClasses(this.tabContainer, "drag-over-target"); - const activetab = this.tabs.find((tab) => tab.hasActiveDragEvent); + const activetab = this.tabs.find((tab) => tab.value.hasActiveDragEvent); const ignore = !!( - activetab && event.composedPath().find((x) => activetab.element === x) + activetab && + event.composedPath().find((x) => activetab.value.element === x) ); if (ignore) { @@ -155,13 +162,16 @@ export class TabContainer extends CompositeDisposable implements ITabContainer { this.active = isGroupActive; } - private addTab(tab: ITab, index: number = this.tabs.length) { + private addTab( + tab: IValueDisposable, + index: number = this.tabs.length + ) { if (index < 0 || index > this.tabs.length) { throw new Error("invalid location"); } this.tabContainer.insertBefore( - tab.element, + tab.value.element, this.tabContainer.children[index] ); @@ -173,28 +183,31 @@ export class TabContainer extends CompositeDisposable implements ITabContainer { } public delete(id: string) { - const index = this.tabs.findIndex((tab) => tab.id === id); + const index = this.tabs.findIndex((tab) => tab.value.id === id); const tab = this.tabs.splice(index, 1)[0]; - tab.element.remove(); + + const { value, disposable } = tab; + + disposable.dispose(); + value.element.remove(); } - public setActivePanel(panel: IPanel) { + public setActivePanel(panel: IGroupPanel) { this.tabs.forEach((tab) => { - const isActivePanel = panel.id === tab.id; - tab.setActive(isActivePanel); + const isActivePanel = panel.id === tab.value.id; + tab.value.setActive(isActivePanel); }); } - public openPanel(panel: IPanel, index: number = this.tabs.length) { - if (this.tabs.find((tab) => tab.id === panel.id)) { + public openPanel(panel: IGroupPanel, index: number = this.tabs.length) { + if (this.tabs.find((tab) => tab.value.id === panel.id)) { return; } const tab = new Tab(panel.id, this.accessor, this.group); tab.setContent(panel.header.element); - // TODO - dispose of resources - const disposables = CompositeDisposable.from( + const disposable = CompositeDisposable.from( tab.onChanged((event) => { switch (event.kind) { case TabInteractionKind.CLICK: @@ -209,15 +222,22 @@ export class TabContainer extends CompositeDisposable implements ITabContainer { }) ); - this.addTab(tab, index); + const value: IValueDisposable = { value: tab, disposable }; + + this.addTab(value, index); this.activePanel = panel; } - public closePanel(panel: IPanel) { + public closePanel(panel: IGroupPanel) { this.delete(panel.id); } public dispose() { super.dispose(); + + this.tabs.forEach((tab) => { + tab.disposable.dispose(); + }); + this.tabs = []; } } diff --git a/packages/splitview/src/index.ts b/packages/splitview/src/index.ts index 929e0ecde..b22af6c31 100644 --- a/packages/splitview/src/index.ts +++ b/packages/splitview/src/index.ts @@ -1,5 +1,5 @@ export * from "./splitview/splitview"; -export * from "./splitview/paneview"; +export * from "./paneview/paneview"; export * from "./gridview/gridview"; export * from "./groupview/groupview"; export * from "./groupview/panel/content/content"; diff --git a/packages/splitview/src/layout/componentFactory.ts b/packages/splitview/src/layout/componentFactory.ts index 2a8ad825e..5f50037ef 100644 --- a/packages/splitview/src/layout/componentFactory.ts +++ b/packages/splitview/src/layout/componentFactory.ts @@ -4,6 +4,7 @@ import { PanelHeaderPart, PanelHeaderPartConstructor, } from "../groupview/panel/parts"; +import { FrameworkFactory } from "../types"; import { DefaultTab } from "./components/tab/defaultTab"; export function createContentComponent( @@ -14,7 +15,7 @@ export function createContentComponent( frameworkComponents: { [componentName: string]: any; }, - createFrameworkComponent: (id: string, component: any) => PanelContentPart + createFrameworkComponent: FrameworkFactory ): PanelContentPart { const Component = typeof componentName === "string" @@ -35,7 +36,7 @@ export function createContentComponent( "you must register a frameworkPanelWrapper to use framework components" ); } - const wrappedComponent = createFrameworkComponent( + const wrappedComponent = createFrameworkComponent.createComponent( componentName, FrameworkComponent ); @@ -52,7 +53,7 @@ export function createTabComponent( frameworkComponents: { [componentName: string]: any; }, - createFrameworkComponent: (id: string, component: any) => PanelHeaderPart + createFrameworkComponent: FrameworkFactory ): PanelHeaderPart { const Component = typeof componentName === "string" @@ -73,7 +74,7 @@ export function createTabComponent( "you must register a frameworkPanelWrapper to use framework components" ); } - const wrappedComponent = createFrameworkComponent( + const wrappedComponent = createFrameworkComponent.createComponent( componentName, FrameworkComponent ); diff --git a/packages/splitview/src/layout/deserializer.ts b/packages/splitview/src/layout/deserializer.ts index e6effec7e..1c15f8152 100644 --- a/packages/splitview/src/layout/deserializer.ts +++ b/packages/splitview/src/layout/deserializer.ts @@ -1,22 +1,22 @@ import { IGridView, IViewDeserializer } from "../gridview/gridview"; -import { IPanel } from "../groupview/panel/types"; +import { IGroupPanel } from "../groupview/panel/types"; import { Layout } from "./layout"; export interface IPanelDeserializer { - fromJSON(panelData: { [index: string]: any }): IPanel; + fromJSON(panelData: { [index: string]: any }): IGroupPanel; } export class DefaultDeserializer implements IViewDeserializer { constructor( private readonly layout: Layout, - private panelDeserializer: { createPanel: (id: string) => IPanel } + private panelDeserializer: { createPanel: (id: string) => IGroupPanel } ) {} public fromJSON(data: { [key: string]: any }): IGridView { const children = data.views; const active = data.activeView; - const panels: IPanel[] = []; + const panels: IGroupPanel[] = []; for (const child of children) { const panel = this.panelDeserializer.createPanel(child); diff --git a/packages/splitview/src/layout/layout.ts b/packages/splitview/src/layout/layout.ts index 50bfdf5cc..513f6df99 100644 --- a/packages/splitview/src/layout/layout.ts +++ b/packages/splitview/src/layout/layout.ts @@ -10,7 +10,7 @@ import { GroupChangeEvent, GroupDropEvent, } from "../groupview/groupview"; -import { IPanel } from "../groupview/panel/types"; +import { IGroupPanel } from "../groupview/panel/types"; import { DefaultPanel } from "../groupview/panel/panel"; import { CompositeDisposable, @@ -103,9 +103,9 @@ export interface IGroupAccessor { activeGroup: IGroupview; // addPanelFromComponent(options: AddPanelOptions): PanelReference; - addPanel(options: AddPanelOptions): IPanel; + addPanel(options: AddPanelOptions): IGroupPanel; // - getPanel: (id: string) => IPanel; + getPanel: (id: string) => IGroupPanel; } export interface ILayout extends IGroupAccessor, Api {} @@ -118,10 +118,10 @@ export class Layout extends CompositeDisposable implements ILayout { private readonly _element: HTMLElement; private readonly _id = nextLayoutId.next(); private readonly groups = new Map>(); - private readonly panels = new Map>(); + private readonly panels = new Map>(); private readonly gridview: Gridview = new Gridview(); - private readonly dirtyPanels = new Set(); - private readonly debouncedDeque = debounce(this.persist.bind(this), 5000); + private readonly dirtyPanels = new Set(); + private readonly debouncedDeque = debounce(this.syncConfigs.bind(this), 5000); // events private readonly _onDidLayoutChange = new Emitter(); readonly onDidLayoutChange: Event = this._onDidLayoutChange @@ -205,7 +205,7 @@ export class Layout extends CompositeDisposable implements ILayout { return this._element; } - public getPanel(id: string): IPanel { + public getPanel(id: string): IGroupPanel { return this.panels.get(id)?.value; } @@ -305,7 +305,7 @@ export class Layout extends CompositeDisposable implements ILayout { this.doSetGroupActive(next); } - public registerPanel(panel: IPanel) { + public registerPanel(panel: IGroupPanel) { if (this.panels.has(panel.id)) { throw new Error(`panel ${panel.id} already exists`); } @@ -319,7 +319,7 @@ export class Layout extends CompositeDisposable implements ILayout { this._onDidLayoutChange.fire({ kind: GroupChangeKind.PANEL_CREATED }); } - public unregisterPanel(panel: IPanel) { + public unregisterPanel(panel: IGroupPanel) { if (!this.panels.has(panel.id)) { throw new Error(`panel ${panel.id} doesn't exist`); } @@ -339,6 +339,8 @@ export class Layout extends CompositeDisposable implements ILayout { * @returns A JSON respresentation of the layout */ public toJSON() { + this.syncConfigs(); + const data = this.gridview.serialize(); const state = { ...this.panelState }; @@ -356,6 +358,43 @@ export class Layout extends CompositeDisposable implements ILayout { return { grid: data, panels }; } + /** + * Ensure the local copy of the layout state is up-to-date + */ + private syncConfigs() { + const dirtyPanels = Array.from(this.dirtyPanels); + + if (dirtyPanels.length === 0) { + console.debug("[layout#syncConfigs] no dirty panels"); + } + + this.dirtyPanels.clear(); + + const partialPanelState = dirtyPanels + .map((panel) => this.panels.get(panel.id)) + .filter((_) => !!_) + .reduce((collection, panel) => { + collection[panel.value.id] = panel.value.toJSON(); + return collection; + }, {}); + + this.panelState = { + ...this.panelState, + ...partialPanelState, + }; + + dirtyPanels + .filter((p) => this.panels.has(p.id)) + .forEach((panel) => { + panel.setDirty(false); + this._onDidLayoutChange.fire({ kind: GroupChangeKind.PANEL_CLEAN }); + }); + + this._onDidLayoutChange.fire({ + kind: GroupChangeKind.LAYOUT_CONFIG_UPDATED, + }); + } + public deserialize(data: any) { this.gridview.clear(); this.panels.forEach((panel) => { @@ -469,7 +508,7 @@ export class Layout extends CompositeDisposable implements ILayout { }; } - public addPanel(options: AddPanelOptions): IPanel { + public addPanel(options: AddPanelOptions): IGroupPanel { const component = this.createContentComponent(options.componentName); const tabComponent = this.createTabComponent(options.tabComponentName); @@ -491,7 +530,7 @@ export class Layout extends CompositeDisposable implements ILayout { componentName, this.options.components, this.options.frameworkComponents, - this.options.frameworkPanelWrapper.createContentWrapper + this.options.frameworkComponentFactory.content ); } @@ -502,7 +541,7 @@ export class Layout extends CompositeDisposable implements ILayout { componentName, this.options.tabComponents, this.options.frameworkTabComponents, - this.options.frameworkPanelWrapper.createTabWrapper + this.options.frameworkComponentFactory.tab ); } @@ -544,7 +583,7 @@ export class Layout extends CompositeDisposable implements ILayout { this.doRemoveGroup(group); } - private addPanelToNewGroup(panel: IPanel, location: number[] = [0]) { + private addPanelToNewGroup(panel: IGroupPanel, location: number[] = [0]) { let group: IGroupview; if ( @@ -735,48 +774,19 @@ export class Layout extends CompositeDisposable implements ILayout { this.gridview.layout(size, orthogonalSize); } - private findGroup(panel: IPanel): IGroupview | undefined { + private findGroup(panel: IGroupPanel): IGroupview | undefined { return Array.from(this.groups.values()).find((group) => group.value.containsPanel(panel) ).value; } - private addDirtyPanel(panel: IPanel) { + private addDirtyPanel(panel: IGroupPanel) { this.dirtyPanels.add(panel); panel.setDirty(true); this._onDidLayoutChange.fire({ kind: GroupChangeKind.PANEL_DIRTY }); this.debouncedDeque(); } - private persist() { - const dirtyPanels = Array.from(this.dirtyPanels); - this.dirtyPanels.clear(); - - const partialPanelState = dirtyPanels - .map((p) => this.panels.get(p.id)) - .filter((_) => !!_) - .reduce((collection, panel) => { - collection[panel.value.id] = panel.value.toJSON(); - return collection; - }, {}); - - this.panelState = { - ...this.panelState, - ...partialPanelState, - }; - - dirtyPanels - .filter((p) => this.panels.has(p.id)) - .forEach((panel) => { - panel.setDirty(false); - this._onDidLayoutChange.fire({ kind: GroupChangeKind.PANEL_CLEAN }); - }); - - this._onDidLayoutChange.fire({ - kind: GroupChangeKind.LAYOUT_CONFIG_UPDATED, - }); - } - private toTarget(direction: "left" | "right" | "above" | "below" | "within") { switch (direction) { case "left": diff --git a/packages/splitview/src/layout/options.ts b/packages/splitview/src/layout/options.ts index 60d19b7f3..31a892d5d 100644 --- a/packages/splitview/src/layout/options.ts +++ b/packages/splitview/src/layout/options.ts @@ -7,19 +7,20 @@ import { PanelHeaderPartConstructor, WatermarkConstructor, } from "../groupview/panel/parts"; -import { IPanel } from "../groupview/panel/types"; +import { IGroupPanel } from "../groupview/panel/types"; +import { FrameworkFactory } from "../types"; import { Api } from "./layout"; -export interface FrameworkPanelWrapper { - createContentWrapper: (id: string, component: any) => PanelContentPart; - createTabWrapper: (id: string, component: any) => PanelHeaderPart; +export interface FrameworkComponentFactory { + content: FrameworkFactory; + tab: FrameworkFactory; } export interface TabContextMenuEvent { event: MouseEvent; api: Api; panelApi: PanelApi; - panel: IPanel; + panel: IGroupPanel; } export interface LayoutOptions { @@ -37,7 +38,7 @@ export interface LayoutOptions { }; watermarkComponent?: WatermarkConstructor; watermarkFrameworkComponent?: any; - frameworkPanelWrapper: FrameworkPanelWrapper; + frameworkComponentFactory: FrameworkComponentFactory; tabHeight?: number; debug?: boolean; enableExternalDragEvents?: boolean; diff --git a/packages/splitview/src/panel/api.ts b/packages/splitview/src/panel/api.ts new file mode 100644 index 000000000..ee05f54b8 --- /dev/null +++ b/packages/splitview/src/panel/api.ts @@ -0,0 +1,71 @@ +import { Emitter, Event } from "../events"; +import { CompositeDisposable, IDisposable } from "../lifecycle"; + +export interface PanelDimensionChangeEvent { + width: number; + height: number; +} + +// try and do a bit better than the 'any' type. +// anything that is serializable JSON should be valid +type StateObject = + | number + | string + | boolean + | undefined + | null + | object + | StateObject[] + | { [key: string]: StateObject }; + +export interface IBasePanelApi extends IDisposable { + // events + onDidPanelDimensionChange: Event; + // state + setState(key: string, value: StateObject): void; + setState(state: { [key: string]: StateObject }): void; + getState: () => { [key: string]: StateObject }; + getStateKey: (key: string) => T; + onDidStateChange: Event; +} +export class BasePanelApi extends CompositeDisposable implements IBasePanelApi { + private _state: { [key: string]: StateObject } = {}; + + private readonly _onDidStateChange = new Emitter(); + readonly onDidStateChange: Event = this._onDidStateChange.event; + + get onDidPanelDimensionChange() { + return this._dimensionEvent; + } + + constructor(private _dimensionEvent: Event) { + super(); + } + + public setState( + key: string | { [key: string]: StateObject }, + value?: StateObject + ) { + if (typeof key === "object") { + this._state = key; + } else { + this._state[key] = value; + } + this._onDidStateChange.fire(undefined); + } + + public getState(): { [key: string]: StateObject } { + return this._state; + } + + public getStateKey(key: string) { + // TODO - find an alternative to 'as any' + return this._state[key] as any; + } + + public dispose() { + super.dispose(); + + this._onDidStateChange.dispose(); + } +} diff --git a/packages/splitview/src/panel/types.ts b/packages/splitview/src/panel/types.ts new file mode 100644 index 000000000..2132eec12 --- /dev/null +++ b/packages/splitview/src/panel/types.ts @@ -0,0 +1,14 @@ +export interface InitParameters { + params: { [index: string]: any }; + state?: { [index: string]: any }; +} + +export interface PanelUpdateEvent { + params: { [index: string]: any }; +} + +export interface IPanel { + init?(params: InitParameters): void; + layout?(width: number, height: number): void; + update?(event: PanelUpdateEvent): void; +} diff --git a/packages/splitview/src/splitview/paneview.scss b/packages/splitview/src/paneview/paneview.scss similarity index 100% rename from packages/splitview/src/splitview/paneview.scss rename to packages/splitview/src/paneview/paneview.scss diff --git a/packages/splitview/src/splitview/paneview.ts b/packages/splitview/src/paneview/paneview.ts similarity index 99% rename from packages/splitview/src/splitview/paneview.ts rename to packages/splitview/src/paneview/paneview.ts index 1b6417fff..6a5b30c6d 100644 --- a/packages/splitview/src/splitview/paneview.ts +++ b/packages/splitview/src/paneview/paneview.ts @@ -1,4 +1,4 @@ -import { SplitView, IView, Orientation } from "./splitview"; +import { SplitView, IView, Orientation } from "../splitview/splitview"; import { IDisposable } from "../lifecycle"; import { Emitter } from "../events"; import { addClasses, removeClasses } from "../dom"; diff --git a/packages/splitview/src/react/deserializer.ts b/packages/splitview/src/react/deserializer.ts index 922ecf749..d377c08b7 100644 --- a/packages/splitview/src/react/deserializer.ts +++ b/packages/splitview/src/react/deserializer.ts @@ -1,4 +1,4 @@ -import { IPanel } from "../groupview/panel/types"; +import { IGroupPanel } from "../groupview/panel/types"; import { Layout } from "../layout/layout"; import { DefaultPanel } from "../groupview/panel/panel"; import { PanelContentPart, PanelHeaderPart } from "../groupview/panel/parts"; @@ -11,7 +11,7 @@ import { export class ReactPanelDeserialzier implements IPanelDeserializer { constructor(private readonly layout: Layout) {} - public fromJSON(panelData: { [index: string]: any }): IPanel { + public fromJSON(panelData: { [index: string]: any }): IGroupPanel { const panelId = panelData.id; const content = panelData.content; const tab = panelData.tab; @@ -24,14 +24,14 @@ export class ReactPanelDeserialzier implements IPanelDeserializer { content.id, this.layout.options.components, this.layout.options.frameworkComponents, - this.layout.options.frameworkPanelWrapper.createContentWrapper + this.layout.options.frameworkComponentFactory.content ) as PanelContentPart; const headerPart = createTabComponent( tab.id, this.layout.options.tabComponents, - this.layout.options.frameworkPanelWrapper, - this.layout.options.frameworkPanelWrapper.createTabWrapper + this.layout.options.frameworkComponentFactory, + this.layout.options.frameworkComponentFactory.tab ) as PanelHeaderPart; const panel = new DefaultPanel(panelId, headerPart, contentPart); diff --git a/packages/splitview/src/react/layout.tsx b/packages/splitview/src/react/layout.tsx index eb83b572d..c6b118798 100644 --- a/packages/splitview/src/react/layout.tsx +++ b/packages/splitview/src/react/layout.tsx @@ -5,6 +5,7 @@ import { ReactPanelContentPart } from "./reactContentPart"; import { ReactPanelHeaderPart } from "./reactHeaderPart"; import { IPanelProps } from "./react"; import { ReactPanelDeserialzier } from "./deserializer"; +import { FrameworkComponentFactory } from "../layout/options"; export interface OnReadyEvent { api: Api; @@ -55,23 +56,27 @@ export const ReactGrid = (props: IReactGridProps) => { }; }; - const frameworkPanelWrapper = { - createContentWrapper: ( - id: string, - component: React.FunctionComponent - ) => { - return new ReactPanelContentPart(id, component, { addPortal }); + const frameworkPanelWrapper: FrameworkComponentFactory = { + content: { + createComponent: ( + id: string, + component: React.FunctionComponent + ) => { + return new ReactPanelContentPart(id, component, { addPortal }); + }, }, - createTabWrapper: ( - id: string, - component: React.FunctionComponent - ) => { - return new ReactPanelHeaderPart(id, component, { addPortal }); + tab: { + createComponent: ( + id: string, + component: React.FunctionComponent + ) => { + return new ReactPanelHeaderPart(id, component, { addPortal }); + }, }, }; const layout = new Layout({ - frameworkPanelWrapper, + frameworkComponentFactory: frameworkPanelWrapper, frameworkComponents: props.components, frameworkTabComponents: props.tabComponents, tabHeight: props.tabHeight, diff --git a/packages/splitview/src/react/react.tsx b/packages/splitview/src/react/react.tsx index d96995486..2269628ac 100644 --- a/packages/splitview/src/react/react.tsx +++ b/packages/splitview/src/react/react.tsx @@ -3,6 +3,7 @@ import * as ReactDOM from "react-dom"; import { IDisposable } from "../lifecycle"; import { PanelApi } from "../groupview/panel/api"; import { sequentialNumberGenerator } from "../math"; +import { IBasePanelApi } from "../panel/api"; export interface IPanelProps { api: PanelApi; @@ -53,9 +54,9 @@ export class ReactPart implements IDisposable { constructor( private readonly parent: HTMLElement, - private readonly api: PanelApi, + private readonly api: IBasePanelApi, private readonly addPortal: (portal: React.ReactPortal) => IDisposable, - private readonly component: React.FunctionComponent, + private readonly component: React.FunctionComponent<{}>, private readonly parameters: { [key: string]: any } ) { this.createPortal(); @@ -77,7 +78,7 @@ export class ReactPart implements IDisposable { let props = { api: this.api, ...this.parameters, - } as IPanelProps; + } as any; const wrapper = React.createElement(PanelWrapper, { component: this.component, diff --git a/packages/splitview/src/react/reactComponentView.ts b/packages/splitview/src/react/reactComponentView.ts new file mode 100644 index 000000000..f063e75f4 --- /dev/null +++ b/packages/splitview/src/react/reactComponentView.ts @@ -0,0 +1,102 @@ +import { trackFocus } from "../dom"; +import { Emitter } from "../events"; +import { BasePanelApi, PanelDimensionChangeEvent } from "../panel/api"; +import { CompositeDisposable } from "../lifecycle"; +import { IView } from "../splitview/splitview"; +import { ReactLayout } from "./layout"; +import { ReactPart } from "./react"; +import { ISplitviewPanelProps } from "./splitview"; +import { PanelUpdateEvent, InitParameters, IPanel } from "../panel/types"; + +/** + * A no-thrills implementation of IView that renders a React component + */ +export class ReactComponentView + extends CompositeDisposable + implements IView, IPanel { + private _element: HTMLElement; + private part: ReactPart; + private params: { params: any }; + private api: BasePanelApi; + private readonly _onDidPanelDimensionsChange = new Emitter< + PanelDimensionChangeEvent + >(); + + private _onDidChange: Emitter = 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 componentName: string, + private readonly component: React.FunctionComponent, + private readonly parent: ReactLayout + ) { + super(); + this.api = new BasePanelApi(this._onDidPanelDimensionsChange.event); + if (!this.component) { + throw new Error("React.FunctionalComponent cannot be undefined"); + } + + this._element = document.createElement("div"); + + const { onDidFocus } = trackFocus(this.element); + + this.addDisposables( + this._onDidPanelDimensionsChange, + onDidFocus(() => { + // + }) + ); + } + + layout(width: number, height: number) { + this._onDidPanelDimensionsChange.fire({ width, height }); + } + + init(parameters: InitParameters): void { + this.params = parameters; + this.part = new ReactPart( + this.element, + this.api, + this.parent.addPortal, + this.component, + parameters.params + ); + } + + update(params: PanelUpdateEvent) { + this.params = { ...this.params.params, ...params }; + this.part.update(params); + } + + toJSON(): object { + return { + id: this.id, + component: this.componentName, + props: this.params.params, + state: this.api.getState(), + }; + } + + dispose() { + this._onDidPanelDimensionsChange.dispose(); + this.api.dispose(); + } +} diff --git a/packages/splitview/src/react/reactView.ts b/packages/splitview/src/react/reactView.ts deleted file mode 100644 index db91aa68d..000000000 --- a/packages/splitview/src/react/reactView.ts +++ /dev/null @@ -1,57 +0,0 @@ -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 = 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); - } -} diff --git a/packages/splitview/src/react/splitview.tsx b/packages/splitview/src/react/splitview.tsx index 54e2ff18c..2a762431d 100644 --- a/packages/splitview/src/react/splitview.tsx +++ b/packages/splitview/src/react/splitview.tsx @@ -1,25 +1,44 @@ import * as React from "react"; -import { Orientation, SplitView } from "../splitview/splitview"; -import { ReactView } from "./reactView"; +import { IBasePanelApi } from "../panel/api"; +import { IDisposable } from "../lifecycle"; +import { + IComponentSplitview, + ComponentSplitview, +} from "../splitview/componentSplitview"; +import { Orientation } from "../splitview/splitview"; +import { ReactComponentView } from "./reactComponentView"; export interface SplitviewFacade { - addFromComponent(options: { id: string; component: string }): void; + addFromComponent(options: { + id: string; + component: string; + params?: { [index: string]: any }; + }): void; layout(size: number, orthogonalSize: number): void; + onChange: (cb: (event: { proportions: number[] }) => void) => IDisposable; + toJSON: () => any; + deserialize: (data: any) => void; } export interface SplitviewReadyEvent { - api: SplitviewFacade; + api: IComponentSplitview; +} + +export interface ISplitviewPanelProps { + api: IBasePanelApi; } export interface ISplitviewComponentProps { orientation: Orientation; onReady?: (event: SplitviewReadyEvent) => void; - components: { [index: string]: React.FunctionComponent<{}> }; + components: { + [index: string]: React.FunctionComponent; + }; } export const SplitViewComponent = (props: ISplitviewComponentProps) => { const domReference = React.useRef(); - const splitview = React.useRef(); + const splitpanel = React.useRef(); const [portals, setPortals] = React.useState([]); const addPortal = React.useCallback((p: React.ReactPortal) => { @@ -32,52 +51,29 @@ export const SplitViewComponent = (props: ISplitviewComponentProps) => { }, []); React.useEffect(() => { - splitview.current = new SplitView(domReference.current, { + splitpanel.current = new ComponentSplitview(domReference.current, { orientation: props.orientation, + frameworkComponents: props.components, + frameworkWrapper: { + createComponent: (id: string, component: any) => { + return new ReactComponentView(id, id, component, { addPortal }); + }, + }, }); - 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); + splitpanel.current.layout(size, orthogonalSize); if (props.onReady) { - props.onReady({ api: facade }); + props.onReady({ api: splitpanel.current }); } return () => { - splitview.current.dispose(); + splitpanel.current.dispose(); }; }, []); diff --git a/packages/splitview/src/splitview/componentSplitview.ts b/packages/splitview/src/splitview/componentSplitview.ts new file mode 100644 index 000000000..cce83bed8 --- /dev/null +++ b/packages/splitview/src/splitview/componentSplitview.ts @@ -0,0 +1,128 @@ +import { IDisposable } from "../lifecycle"; +import { Orientation, SplitView } from "./splitview"; +import { + createComponent, + ISerializableView, + SplitPanelOptions, +} from "./options"; + +export interface IComponentSplitview extends IDisposable { + addFromComponent(options: { + id: string; + component: string; + params?: { + [index: string]: any; + }; + }): IDisposable; + layout(width: number, height: number): void; + onChange(cb: (event: { proportions: number[] }) => void): IDisposable; + toJSON(): object; + deserialize(data: any): void; +} + +/** + * A high-level implementation of splitview that works using 'panels' + */ +export class ComponentSplitview implements IComponentSplitview { + private splitview: SplitView; + + constructor( + private readonly element: HTMLElement, + private readonly options: SplitPanelOptions + ) { + if (!options.components) { + options.components = {}; + } + if (!options.frameworkComponents) { + options.frameworkComponents = {}; + } + + this.splitview = new SplitView(this.element, options); + } + + addFromComponent(options: { + id: string; + component: string; + params?: { + [index: string]: any; + }; + }): IDisposable { + const view = createComponent( + options.component, + this.options.components, + this.options.frameworkComponents, + this.options.frameworkWrapper.createComponent + ); + + this.registerView(view); + + this.splitview.addView(view, { type: "distribute" }); + view.init({ params: options.params }); + return { + dispose: () => { + // + }, + }; + } + + private registerView(view: ISerializableView) { + // + } + + layout(width: number, height: number): void { + const [size, orthogonalSize] = + this.splitview.orientation === Orientation.HORIZONTAL + ? [width, height] + : [height, width]; + this.splitview.layout(size, orthogonalSize); + } + + onChange(cb: (event: { proportions: number[] }) => void): IDisposable { + return this.splitview.onDidSashEnd(() => { + cb({ proportions: this.splitview.proportions }); + }); + } + toJSON(): object { + const views = this.splitview.getViews().map((v: ISerializableView, i) => { + const size = this.splitview.getViewSize(i); + return { size, data: v.toJSON ? v.toJSON() : {} }; + }); + + return { + views, + size: this.splitview.size, + orientation: this.splitview.orientation, + }; + } + deserialize(data: any): void { + const { views, orientation, size } = data; + + this.splitview.dispose(); + this.splitview = new SplitView(this.element, { + orientation, + descriptor: { + size, + views: views.map((v) => { + const data = v.data; + + const view = createComponent( + data.component, + this.options.components, + this.options.frameworkComponents, + this.options.frameworkWrapper.createComponent + ); + + view.init({ params: v.props }); + + return { size: v.size, view }; + }), + }, + }); + + this.splitview.orientation = orientation; + } + + public dispose() { + this.splitview.dispose(); + } +} diff --git a/packages/splitview/src/splitview/options.ts b/packages/splitview/src/splitview/options.ts new file mode 100644 index 000000000..6800898b8 --- /dev/null +++ b/packages/splitview/src/splitview/options.ts @@ -0,0 +1,58 @@ +import { IView, ISplitViewOptions } from "../splitview/splitview"; +import { Constructor, FrameworkFactory } from "../types"; + +export interface ISerializableView extends IView { + toJSON: () => object; + init: (params: { params: any }) => void; +} + +export interface SplitPanelOptions extends ISplitViewOptions { + components?: { + [componentName: string]: ISerializableView; + }; + frameworkComponents?: { + [componentName: string]: any; + }; + frameworkWrapper?: FrameworkFactory; +} + +export interface ISerializableViewConstructor + extends Constructor {} + +export function createComponent( + componentName: string | ISerializableViewConstructor | any, + components: { + [componentName: string]: ISerializableView; + }, + frameworkComponents: { + [componentName: string]: any; + }, + createFrameworkComponent: (id: string, component: any) => ISerializableView +): ISerializableView { + const Component = + typeof componentName === "string" + ? components[componentName] + : componentName; + const FrameworkComponent = + typeof componentName === "string" + ? frameworkComponents[componentName] + : componentName; + if (Component && FrameworkComponent) { + throw new Error( + `cannot register component ${componentName} as both a component and frameworkComponent` + ); + } + if (FrameworkComponent) { + if (!createFrameworkComponent) { + throw new Error( + "you must register a frameworkPanelWrapper to use framework components" + ); + } + const wrappedComponent = createFrameworkComponent( + componentName, + FrameworkComponent + ); + return wrappedComponent; + } + return new Component() as ISerializableView; +} diff --git a/packages/splitview/src/splitview/splitview.ts b/packages/splitview/src/splitview/splitview.ts index d8d70e8b5..7c72cd087 100644 --- a/packages/splitview/src/splitview/splitview.ts +++ b/packages/splitview/src/splitview/splitview.ts @@ -68,15 +68,23 @@ export class SplitView { private sashContainer: HTMLElement; private views: IViewItem[] = []; private sashes: ISashItem[] = []; - private orientation: Orientation; - private size: number; - private orthogonalSize: number; + private _orientation: Orientation; + private _size: number; + private _orthogonalSize: number; private contentSize: number; private _proportions: number[]; private _onDidSashEnd = new Emitter(); public onDidSashEnd = this._onDidSashEnd.event; + get size() { + return this._size; + } + + get orthogonalSize() { + return this._orthogonalSize; + } + public get length() { return this.views.length; } @@ -85,6 +93,10 @@ export class SplitView { return [...this._proportions]; } + get orientation() { + return this._orientation; + } + get minimumSize(): number { return this.views.reduce((r, item) => r + item.view.minimumSize, 0); } @@ -99,7 +111,7 @@ export class SplitView { private readonly container: HTMLElement, options: ISplitViewOptions ) { - this.orientation = options.orientation; + this._orientation = options.orientation; this.element = this.createContainer(); this.viewContainer = this.createViewContainer(); @@ -112,7 +124,7 @@ export class SplitView { // We have an existing set of view, add them now if (options.descriptor) { - this.size = options.descriptor.size; + this._size = options.descriptor.size; options.descriptor.views.forEach((viewDescriptor, index) => { const sizing = viewDescriptor.size; @@ -161,7 +173,7 @@ export class SplitView { size = clamp( size, item.view.minimumSize, - Math.min(item.view.maximumSize, this.size) + Math.min(item.view.maximumSize, this._size) ); item.size = size; @@ -189,7 +201,7 @@ export class SplitView { const contentSize = this.views.reduce((r, i) => r + i.size, 0); - this.resize(this.views.length - 1, this.size - contentSize, undefined, [ + this.resize(this.views.length - 1, this._size - contentSize, undefined, [ index, ]); this.distributeEmptySpace(); @@ -250,7 +262,7 @@ export class SplitView { const cb = (event: MouseEvent) => { let start = - this.orientation === Orientation.HORIZONTAL + this._orientation === Orientation.HORIZONTAL ? event.clientX : event.clientY; const sizes = this.views.map((x) => x.size); @@ -259,7 +271,7 @@ export class SplitView { const mousemove = (event: MouseEvent) => { const current = - this.orientation === Orientation.HORIZONTAL + this._orientation === Orientation.HORIZONTAL ? event.clientX : event.clientY; const delta = current - start; @@ -370,11 +382,11 @@ export class SplitView { this.addView(view, sizing, to); } - public setOrientation(orientation: Orientation) { - if (orientation === this.orientation) { + set orientation(orientation: Orientation) { + if (orientation === this._orientation) { return; } - this.orientation = orientation; + this._orientation = orientation; const classname = orientation === Orientation.HORIZONTAL ? "horizontal" : "vertical"; @@ -386,8 +398,8 @@ export class SplitView { } public layout(size: number, orthogonalSize: number) { - this.size = size; - this.orthogonalSize = orthogonalSize; + this._size = size; + this._orthogonalSize = orthogonalSize; for (let i = 0; i < this.views.length; i++) { const item = this.views[i]; @@ -412,7 +424,7 @@ export class SplitView { this.resize( this.views.length - 1, - this.size - contentSize, + this._size - contentSize, undefined, lowPriorityIndexes, highPriorityIndexes @@ -423,7 +435,7 @@ export class SplitView { private distributeEmptySpace() { let contentSize = this.views.reduce((r, i) => r + i.size, 0); - let emptyDelta = this.size - contentSize; + let emptyDelta = this._size - contentSize; for (let i = this.views.length - 1; emptyDelta !== 0 && i >= 0; i--) { const item = this.views[i]; @@ -448,30 +460,30 @@ export class SplitView { for (let i = 0; i < this.views.length - 1; i++) { sum += this.views[i].size; x.push(sum); - if (this.orientation === Orientation.HORIZONTAL) { + if (this._orientation === Orientation.HORIZONTAL) { this.sashes[i].container.style.left = `${sum - 2}px`; this.sashes[i].container.style.top = `0px`; } - if (this.orientation === Orientation.VERTICAL) { + if (this._orientation === Orientation.VERTICAL) { this.sashes[i].container.style.left = `0px`; this.sashes[i].container.style.top = `${sum - 2}px`; } } this.views.forEach((view, i) => { - if (this.orientation === Orientation.HORIZONTAL) { + if (this._orientation === Orientation.HORIZONTAL) { view.container.style.width = `${view.size}px`; view.container.style.left = i == 0 ? "0px" : `${x[i - 1]}px`; view.container.style.top = ""; view.container.style.height = ""; } - if (this.orientation === Orientation.VERTICAL) { + if (this._orientation === Orientation.VERTICAL) { view.container.style.height = `${view.size}px`; view.container.style.top = i == 0 ? "0px" : `${x[i - 1]}px`; view.container.style.width = ""; view.container.style.left = ""; } - view.view.layout(view.size, this.orthogonalSize); + view.view.layout(view.size, this._orthogonalSize); }); } @@ -588,12 +600,13 @@ export class SplitView { private createContainer() { const element = document.createElement("div"); const orientationClassname = - this.orientation === Orientation.HORIZONTAL ? "horizontal" : "vertical"; + this._orientation === Orientation.HORIZONTAL ? "horizontal" : "vertical"; element.className = `split-view-container ${orientationClassname}`; return element; } public dispose() { + this.element.remove(); for (let i = 0; i < this.element.children.length; i++) { if (this.element.children.item[i] === this.element) { this.element.removeChild(this.element); diff --git a/packages/splitview/src/types.ts b/packages/splitview/src/types.ts new file mode 100644 index 000000000..d24307f8c --- /dev/null +++ b/packages/splitview/src/types.ts @@ -0,0 +1,7 @@ +export interface Constructor { + new (): T; +} + +export interface FrameworkFactory { + createComponent: (id: string, component: any) => T; +} diff --git a/scripts/build.js b/scripts/build.js index 96ae96792..f083e63ba 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -5,6 +5,7 @@ const merge = require("merge2"); const header = require("gulp-header"); const gulpSass = require("gulp-sass"); const concat = require("gulp-concat"); +const sourcemaps = require("gulp-sourcemaps"); const headerTemplate = [ "/**", @@ -28,7 +29,10 @@ const build = (options) => { gulp.task("esm", () => { const ts = gulpTypescript.createProject(tsconfig); - const tsResult = gulp.src(["src/**/*.ts", "src/**/*.tsx"]).pipe(ts()); + const tsResult = gulp + .src(["src/**/*.ts", "src/**/*.tsx"]) + .pipe(sourcemaps.init()) + .pipe(ts()); return merge([ tsResult.dts .pipe(header(dtsHeaderTemplate, { pkg: package })) @@ -36,6 +40,9 @@ const build = (options) => { tsResult.js .pipe(header(headerTemplate, { pkg: package })) .pipe(gulp.dest("./dist/esm")), + tsResult + .pipe(sourcemaps.write(".", { includeContent: false })) + .pipe(gulp.dest("./dist/esm")), ]); });