diff --git a/src/sql/base/browser/ui/modal/modal.ts b/src/sql/base/browser/ui/modal/modal.ts index bd33c5f816..d63bb5260f 100644 --- a/src/sql/base/browser/ui/modal/modal.ts +++ b/src/sql/base/browser/ui/modal/modal.ts @@ -414,17 +414,17 @@ export abstract class Modal extends Disposable implements IThemable { } }); this._resizeListener = DOM.addDisposableListener(window, DOM.EventType.RESIZE, (e: Event) => { - this.layout(DOM.getTotalHeight(this._builder.getHTMLElement())); + this.layout(DOM.getTotalHeight(this._modalBodySection)); }); - this.layout(DOM.getTotalHeight(this._builder.getHTMLElement())); + this.layout(DOM.getTotalHeight(this._modalBodySection)); TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogOpened, { name: this._name }); } /** * Required to be implemented so that scrolling and other functions operate correctly. Should re-layout controls in the modal */ - protected abstract layout(height?: number): void; + protected abstract layout(height: number): void; /** * Hides the modal and removes key listeners diff --git a/src/sql/base/browser/ui/modal/optionsDialog.ts b/src/sql/base/browser/ui/modal/optionsDialog.ts index 30e3767164..f282123d7b 100644 --- a/src/sql/base/browser/ui/modal/optionsDialog.ts +++ b/src/sql/base/browser/ui/modal/optionsDialog.ts @@ -6,19 +6,20 @@ 'use strict'; import 'vs/css!./media/optionsDialog'; -import { Button } from 'sql/base/browser/ui/button/button'; -import { FixedCollapsibleView } from 'sql/platform/views/fixedCollapsibleView'; import * as DialogHelper from './dialogHelper'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; import { IModalOptions, Modal } from './modal'; import * as OptionsDialogHelper from './optionsDialogHelper'; -import { attachButtonStyler, attachModalDialogStyler } from 'sql/common/theme/styler'; +import { attachButtonStyler, attachModalDialogStyler, attachPanelStyler } from 'sql/common/theme/styler'; +import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview'; import * as sqlops from 'sqlops'; + import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { localize } from 'vs/nls'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -26,58 +27,56 @@ import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/theme import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import * as styler from 'vs/platform/theme/common/styler'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { SplitView, CollapsibleState } from 'sql/base/browser/ui/splitview/splitview'; import { Builder, $ } from 'vs/base/browser/builder'; import { Widget } from 'vs/base/browser/ui/widget'; -import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -export class CategoryView extends FixedCollapsibleView { - private _treecontainer: HTMLElement; - private _collapsed: CollapsibleState; +export class CategoryView extends ViewletPanel { - constructor(private viewTitle: string, private _bodyContainer: HTMLElement, collapsed: boolean, initialBodySize: number, headerSize: number) { - super( - initialBodySize, - { - expandedBodySize: initialBodySize, - sizing: headerSize, - initialState: collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED, - ariaHeaderLabel: viewTitle - }); - this._collapsed = collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED; + constructor( + private contentElement: HTMLElement, + private size: number, + options: IViewletPanelOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService + ) { + super(options, keybindingService, contextMenuService, configurationService); } - public renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - $('span').text(this.viewTitle).appendTo(titleDiv); + // we want a fixed size, so when we render to will measure our content and set that to be our + // minimum and max size + protected renderBody(container: HTMLElement): void { + container.appendChild(this.contentElement); + this.maximumBodySize = this.size; + this.minimumBodySize = this.size; } - public renderBody(container: HTMLElement): void { - this._treecontainer = document.createElement('div'); - container.appendChild(this._treecontainer); - this._treecontainer.appendChild(this._bodyContainer); - this.changeState(this._collapsed); + protected layoutBody(size: number): void { + // } +} - public layoutBody(size: number): void { - this._treecontainer.style.height = size + 'px'; - } +export interface IOptionsDialogOptions extends IModalOptions { + cancelLabel?: string; } export class OptionsDialog extends Modal { private _body: HTMLElement; private _optionGroups: HTMLElement; private _dividerBuilder: Builder; - private _okButton: Button; - private _closeButton: Button; private _optionTitle: Builder; private _optionDescription: Builder; private _optionElements: { [optionName: string]: OptionsDialogHelper.IOptionElement } = {}; private _optionValues: { [optionName: string]: string }; private _optionRowSize = 31; private _optionCategoryPadding = 30; - private _categoryHeaderSize = 22; + private height: number; + private splitview: ScrollableSplitView; private _onOk = new Emitter(); public onOk: Event = this._onOk.event; @@ -85,16 +84,14 @@ export class OptionsDialog extends Modal { private _onCloseEvent = new Emitter(); public onCloseEvent: Event = this._onCloseEvent.event; - public okLabel: string = localize('optionsDialog.ok', 'OK'); - public cancelLabel: string = localize('optionsDialog.cancel', 'Cancel'); - constructor( title: string, name: string, - options: IModalOptions, + private options: IOptionsDialogOptions, @IPartService partService: IPartService, @IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService, @IContextViewService private _contextViewService: IContextViewService, + @IInstantiationService private _instantiationService: IInstantiationService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService clipboardService: IClipboardService @@ -109,14 +106,13 @@ export class OptionsDialog extends Modal { this.backButton.onDidClick(() => this.cancel()); attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }); } - this._okButton = this.addFooterButton(this.okLabel, () => this.ok()); - this._closeButton = this.addFooterButton(this.cancelLabel, () => this.cancel()); + let okButton = this.addFooterButton(localize('optionsDialog.ok', 'OK'), () => this.ok()); + let closeButton = this.addFooterButton(this.options.cancelLabel || localize('optionsDialog.cancel', 'Cancel'), () => this.cancel()); // Theme styler - attachButtonStyler(this._okButton, this._themeService); - attachButtonStyler(this._closeButton, this._themeService); - let self = this; - this._register(self._workbenchThemeService.onDidColorThemeChange(e => self.updateTheme(e))); - self.updateTheme(self._workbenchThemeService.getColorTheme()); + attachButtonStyler(okButton, this._themeService); + attachButtonStyler(closeButton, this._themeService); + this._register(this._workbenchThemeService.onDidColorThemeChange(e => this.updateTheme(e))); + this.updateTheme(this._workbenchThemeService.getColorTheme()); } protected renderBody(container: HTMLElement) { @@ -151,24 +147,24 @@ export class OptionsDialog extends Modal { } private onOptionLinkClicked(optionName: string): void { - var option = this._optionElements[optionName].option; + let option = this._optionElements[optionName].option; this._optionTitle.text(option.displayName); this._optionDescription.text(option.description); } private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void { - for (var i = 0; i < options.length; i++) { - var option: sqlops.ServiceOption = options[i]; - var rowContainer = DialogHelper.appendRow(container, option.displayName, 'optionsDialog-label', 'optionsDialog-input'); + for (let i = 0; i < options.length; i++) { + let option: sqlops.ServiceOption = options[i]; + let rowContainer = DialogHelper.appendRow(container, option.displayName, 'optionsDialog-label', 'optionsDialog-input'); OptionsDialogHelper.createOptionElement(option, rowContainer, this._optionValues, this._optionElements, this._contextViewService, (name) => this.onOptionLinkClicked(name)); } } private registerStyling(): void { // Theme styler - for (var optionName in this._optionElements) { - var widget: Widget = this._optionElements[optionName].optionWidget; - var option = this._optionElements[optionName].option; + for (let optionName in this._optionElements) { + let widget: Widget = this._optionElements[optionName].optionWidget; + let option = this._optionElements[optionName].option; switch (option.valueType) { case ServiceOptionType.category: case ServiceOptionType.boolean: @@ -225,47 +221,51 @@ export class OptionsDialog extends Modal { public open(options: sqlops.ServiceOption[], optionValues: { [name: string]: any }) { this._optionValues = optionValues; - var firstOption: string; - var containerGroup: Builder; - var layoutSize = 0; - var optionsContentBuilder: Builder = $().div({ class: 'optionsDialog-options-groups' }, (container) => { + let firstOption: string; + let containerGroup: Builder; + let optionsContentBuilder: Builder = $().div({ class: 'optionsDialog-options-groups monaco-panel-view' }, (container) => { containerGroup = container; this._optionGroups = container.getHTMLElement(); }); - var splitview = new SplitView(containerGroup.getHTMLElement()); + this.splitview = new ScrollableSplitView(containerGroup.getHTMLElement(), { enableResizing: false, scrollDebounce: 0 }); let categoryMap = OptionsDialogHelper.groupOptionsByCategory(options); - for (var category in categoryMap) { - var serviceOptions: sqlops.ServiceOption[] = categoryMap[category]; - var bodyContainer = $().element('table', { class: 'optionsDialog-table' }, (tableContainer: Builder) => { + for (let category in categoryMap) { + let serviceOptions: sqlops.ServiceOption[] = categoryMap[category]; + let bodyContainer = $().element('table', { class: 'optionsDialog-table' }, (tableContainer: Builder) => { this.fillInOptions(tableContainer, serviceOptions); }); - var viewSize = this._optionCategoryPadding + serviceOptions.length * this._optionRowSize; - layoutSize += (viewSize + this._categoryHeaderSize); - var categoryView = new CategoryView(category, bodyContainer.getHTMLElement(), false, viewSize, this._categoryHeaderSize); - splitview.addView(categoryView); + let viewSize = this._optionCategoryPadding + serviceOptions.length * this._optionRowSize; + let categoryView = this._instantiationService.createInstance(CategoryView, bodyContainer.getHTMLElement(), viewSize, { title: category, ariaHeaderLabel: category, id: category }); + this.splitview.addView(categoryView, viewSize); + categoryView.render(); + attachPanelStyler(categoryView, this._themeService); if (!firstOption) { firstOption = serviceOptions[0].name; } } - splitview.layout(layoutSize); + if (this.height) { + this.splitview.layout(this.height - 120); + } let body = new Builder(this._body); body.append(optionsContentBuilder.getHTMLElement(), 0); this.show(); - var firstOptionWidget = this._optionElements[firstOption].optionWidget; + let firstOptionWidget = this._optionElements[firstOption].optionWidget; this.registerStyling(); firstOptionWidget.focus(); } protected layout(height?: number): void { - // Nothing currently laid out in this class + this.height = height; + // account for padding and the details view + this.splitview.layout(this.height - 120 - 20); } public dispose(): void { super.dispose(); - for (var optionName in this._optionElements) { - var widget: Widget = this._optionElements[optionName].optionWidget; + for (let optionName in this._optionElements) { + let widget: Widget = this._optionElements[optionName].optionWidget; widget.dispose(); delete this._optionElements[optionName]; } diff --git a/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts b/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts index f78668115d..faec25ae6b 100644 --- a/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts +++ b/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts @@ -6,7 +6,7 @@ import { INextIterator } from 'vs/base/common/iterator'; export interface IView { - id: string; + id?: string; } export interface IViewItem { diff --git a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.css b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.css index 58c0de195f..9e6b717dae 100644 --- a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.css +++ b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.css @@ -3,6 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - .monaco-scroll-split-view { +.split-view-container { position: relative; } + +.monaco-scroll-split-view { + height: 100%; + width: 100%; +} \ No newline at end of file diff --git a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts index 3bd52c6a5c..dc5379d9af 100644 --- a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts +++ b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts @@ -6,35 +6,49 @@ 'use strict'; import 'vs/css!./scrollableSplitview'; +import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap'; + import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { mapEvent, Emitter, Event, debounceEvent } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; import * as dom from 'vs/base/browser/dom'; import { clamp } from 'vs/base/common/numbers'; -import { range, firstIndex } from 'vs/base/common/arrays'; +import { range, firstIndex, pushToStart } from 'vs/base/common/arrays'; import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap'; import { ArrayIterator } from 'vs/base/common/iterator'; import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { ISplitViewStyles, Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import { Color } from 'vs/base/common/color'; +import { domEvent } from 'vs/base/browser/event'; +import { generateUuid } from 'vs/base/common/uuid'; export { Orientation } from 'vs/base/browser/ui/sash/sash'; export interface ISplitViewOptions { orientation?: Orientation; // default Orientation.VERTICAL + styles?: ISplitViewStyles; + orthogonalStartSash?: Sash; + orthogonalEndSash?: Sash; + inverseAltBehavior?: boolean; enableResizing?: boolean; + scrollDebounce?: number; verticalScrollbarVisibility?: ScrollbarVisibility; } +const defaultStyles: ISplitViewStyles = { + separatorBorder: Color.transparent +}; + const defaultOptions: ISplitViewOptions = { enableResizing: true }; export interface IView extends HeightIView { + readonly element: HTMLElement; readonly minimumSize: number; readonly maximumSize: number; readonly onDidChange: Event; - render(container: HTMLElement, orientation: Orientation): void; layout(size: number, orientation: Orientation): void; onAdd?(): void; onRemove?(): void; @@ -44,6 +58,7 @@ interface ISashEvent { sash: Sash; start: number; current: number; + alt: boolean; } interface IViewItem extends HeightIViewItem { @@ -64,7 +79,12 @@ interface ISashItem { interface ISashDragState { index: number; start: number; + current: number; sizes: number[]; + minDelta: number; + maxDelta: number; + alt: boolean; + disposable: IDisposable; } enum State { @@ -95,24 +115,28 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { private orientation: Orientation; private el: HTMLElement; + private sashContainer: HTMLElement; + private viewContainer: HTMLElement; + private scrollable: ScrollableElement; private size = 0; private contentSize = 0; + private proportions: undefined | number[] = undefined; private viewItems: IViewItem[] = []; private sashItems: ISashItem[] = []; private sashDragState: ISashDragState; private state: State = State.Idle; - private scrollable: ScrollableElement; + private inverseAltBehavior: boolean; + + private lastRenderHeight: number; + private lastRenderTop: number; private options: ISplitViewOptions; private dirtyState = false; - private lastRenderTop: number; - private lastRenderHeight: number; - - private _onDidSashChange = new Emitter(); + private _onDidSashChange = new Emitter(); readonly onDidSashChange = this._onDidSashChange.event; - private _onDidSashReset = new Emitter(); + private _onDidSashReset = new Emitter(); readonly onDidSashReset = this._onDidSashReset.event; private _onScroll = new Emitter(); @@ -122,15 +146,48 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { return this.viewItems.length; } + get minimumSize(): number { + return this.viewItems.reduce((r, item) => r + item.view.minimumSize, 0); + } + + get maximumSize(): number { + return this.length === 0 ? Number.POSITIVE_INFINITY : this.viewItems.reduce((r, item) => r + item.view.maximumSize, 0); + } + + private _orthogonalStartSash: Sash | undefined; + get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; } + set orthogonalStartSash(sash: Sash | undefined) { + for (const sashItem of this.sashItems) { + sashItem.sash.orthogonalStartSash = sash; + } + + this._orthogonalStartSash = sash; + } + + private _orthogonalEndSash: Sash | undefined; + get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; } + set orthogonalEndSash(sash: Sash | undefined) { + for (const sashItem of this.sashItems) { + sashItem.sash.orthogonalEndSash = sash; + } + + this._orthogonalEndSash = sash; + } + + get sashes(): Sash[] { + return this.sashItems.map(s => s.sash); + } + constructor(container: HTMLElement, options: ISplitViewOptions = {}) { super(); this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation; + this.inverseAltBehavior = !!options.inverseAltBehavior; this.options = mixin(options, defaultOptions, false); this.el = document.createElement('div'); this.scrollable = new ScrollableElement(this.el, { vertical: options.verticalScrollbarVisibility }); - debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => { + debounceEvent(this.scrollable.onScroll, (l, e) => e, types.isNumber(this.options.scrollDebounce) ? this.options.scrollDebounce : 25)(e => { this.render(e.scrollTop, e.height); this.relayout(); this._onScroll.fire(e.scrollTop); @@ -140,9 +197,24 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { dom.addClass(domNode, 'monaco-split-view2'); dom.addClass(domNode, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal'); container.appendChild(domNode); + + this.sashContainer = dom.append(this.el, dom.$('.sash-container')); + this.viewContainer = dom.append(this.el, dom.$('.split-view-container')); + + this.style(options.styles || defaultStyles); } - addViews(views: IView[], sizes: number[], index = this.viewItems.length): void { + style(styles: ISplitViewStyles): void { + if (styles.separatorBorder.isTransparent()) { + dom.removeClass(this.el, 'separator-border'); + this.el.style.removeProperty('--separator-border'); + } else { + dom.addClass(this.el, 'separator-border'); + this.el.style.setProperty('--separator-border', styles.separatorBorder.toString()); + } + } + + addViews(views: IView[], sizes: number[] | Sizing, index = this.viewItems.length): void { if (this.state !== State.Idle) { throw new Error('Cant modify splitview'); } @@ -150,16 +222,23 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { this.state = State.Busy; for (let i = 0; i < views.length; i++) { - let viewIndex = index + i; - let view = views[i], size = sizes[i]; - + let size: number | Sizing; + if (Array.isArray(sizes)) { + size = sizes[i]; + } else { + size = sizes; + } + const view = views[i]; + view.id = view.id || generateUuid(); // Add view const container = dom.$('.split-view-view'); + // removed default adding of the view directly to the container + const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size)); const containerDisposable = toDisposable(() => { if (container.parentElement) { - this.el.removeChild(container); + this.viewContainer.removeChild(container); } this.onRemoveItems(new ArrayIterator([item.view.id])); }); @@ -169,65 +248,91 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { const onRemove = view.onRemove ? () => view.onRemove() : () => { }; const layoutContainer = this.orientation === Orientation.VERTICAL - ? size => item.container.style.height = `${item.size}px` - : size => item.container.style.width = `${item.size}px`; + ? () => item.container.style.height = `${item.size}px` + : () => item.container.style.width = `${item.size}px`; const layout = () => { - layoutContainer(item.size); + layoutContainer(); item.view.layout(item.size, this.orientation); }; - size = Math.round(size); - const item: IViewItem = { onRemove, onAdd, view, container, size, layout, disposable, height: size, top: 0, width: 0 }; - this.viewItems.splice(viewIndex, 0, item); + let viewSize: number; - this.onInsertItems(new ArrayIterator([item]), viewIndex > 0 ? this.viewItems[viewIndex - 1].view.id : undefined); + if (typeof size === 'number') { + viewSize = size; + } else if (size.type === 'split') { + viewSize = this.getViewSize(size.index) / 2; + } else { + viewSize = view.minimumSize; + } + + const item: IViewItem = { onAdd, onRemove, view, container, size: viewSize, layout, disposable, height: viewSize, top: 0, width: 0 }; + this.viewItems.splice(index, 0, item); + + this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined); // Add sash if (this.options.enableResizing && this.viewItems.length > 1) { const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) }; - const sash = new Sash(this.el, layoutProvider, { orientation }); + const sash = new Sash(this.sashContainer, layoutProvider, { + orientation, + orthogonalStartSash: this.orthogonalStartSash, + orthogonalEndSash: this.orthogonalEndSash + }); + const sashEventMapper = this.orientation === Orientation.VERTICAL - ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY }) - : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX }); + ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent) + : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey } as ISashEvent); const onStart = mapEvent(sash.onDidStart, sashEventMapper); const onStartDisposable = onStart(this.onSashStart, this); const onChange = mapEvent(sash.onDidChange, sashEventMapper); - const onSashChangeDisposable = onChange(this.onSashChange, this); - const onEnd = mapEvent(sash.onDidEnd, () => null); - const onEndDisposable = onEnd(() => this._onDidSashChange.fire()); - const onDidReset = mapEvent(sash.onDidReset, () => null); - const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire()); + const onChangeDisposable = onChange(this.onSashChange, this); + const onEnd = mapEvent(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash)); + const onEndDisposable = onEnd(this.onSashEnd, this); + const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash))); - const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); + const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); const sashItem: ISashItem = { sash, disposable }; - this.sashItems.splice(viewIndex - 1, 0, sashItem); + this.sashItems.splice(index - 1, 0, sashItem); } - view.render(container, this.orientation); + container.appendChild(view.element); } - this.relayout(); + let highPriorityIndex: number | undefined; + + if (!types.isArray(sizes) && sizes.type === 'split') { + highPriorityIndex = sizes.index; + } + + this.relayout(index, highPriorityIndex); this.state = State.Idle; + + if (!types.isArray(sizes) && sizes.type === 'distribute') { + this.distributeViewSizes(); + } } - addView(view: IView, size: number, index = this.viewItems.length): void { + addView(view: IView, size: number | Sizing, index = this.viewItems.length): void { if (this.state !== State.Idle) { throw new Error('Cant modify splitview'); } this.state = State.Busy; + view.id = view.id || generateUuid(); // Add view const container = dom.$('.split-view-view'); + // removed default adding of the view directly to the container + const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size)); const containerDisposable = toDisposable(() => { if (container.parentElement) { - this.el.removeChild(container); + this.viewContainer.removeChild(container); } this.onRemoveItems(new ArrayIterator([item.view.id])); }); @@ -237,16 +342,25 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { const onRemove = view.onRemove ? () => view.onRemove() : () => { }; const layoutContainer = this.orientation === Orientation.VERTICAL - ? size => item.container.style.height = `${item.size}px` - : size => item.container.style.width = `${item.size}px`; + ? () => item.container.style.height = `${item.size}px` + : () => item.container.style.width = `${item.size}px`; const layout = () => { - layoutContainer(item.size); + layoutContainer(); item.view.layout(item.size, this.orientation); }; - size = Math.round(size); - const item: IViewItem = { onAdd, onRemove, view, container, size, layout, disposable, height: size, top: 0, width: 0 }; + let viewSize: number; + + if (typeof size === 'number') { + viewSize = size; + } else if (size.type === 'split') { + viewSize = this.getViewSize(size.index) / 2; + } else { + viewSize = view.minimumSize; + } + + const item: IViewItem = { onAdd, onRemove, view, container, size: viewSize, layout, disposable, height: viewSize, top: 0, width: 0 }; this.viewItems.splice(index, 0, item); this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined); @@ -255,33 +369,47 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { if (this.options.enableResizing && this.viewItems.length > 1) { const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) }; - const sash = new Sash(this.el, layoutProvider, { orientation }); + const sash = new Sash(this.sashContainer, layoutProvider, { + orientation, + orthogonalStartSash: this.orthogonalStartSash, + orthogonalEndSash: this.orthogonalEndSash + }); + const sashEventMapper = this.orientation === Orientation.VERTICAL - ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY }) - : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX }); + ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent) + : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey } as ISashEvent); const onStart = mapEvent(sash.onDidStart, sashEventMapper); const onStartDisposable = onStart(this.onSashStart, this); const onChange = mapEvent(sash.onDidChange, sashEventMapper); - const onSashChangeDisposable = onChange(this.onSashChange, this); - const onEnd = mapEvent(sash.onDidEnd, () => null); - const onEndDisposable = onEnd(() => this._onDidSashChange.fire()); - const onDidReset = mapEvent(sash.onDidReset, () => null); - const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire()); + const onChangeDisposable = onChange(this.onSashChange, this); + const onEnd = mapEvent(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash)); + const onEndDisposable = onEnd(this.onSashEnd, this); + const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash))); - const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); + const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); const sashItem: ISashItem = { sash, disposable }; - sash.hide(); this.sashItems.splice(index - 1, 0, sashItem); } - view.render(container, this.orientation); - this.relayout(index); + container.appendChild(view.element); + + let highPriorityIndex: number | undefined; + + if (typeof size !== 'number' && size.type === 'split') { + highPriorityIndex = size.index; + } + + this.relayout(index, highPriorityIndex); this.state = State.Idle; + + if (typeof size !== 'number' && size.type === 'distribute') { + this.distributeViewSizes(); + } } - removeView(index: number): void { + removeView(index: number, sizing?: Sizing): IView { if (this.state !== State.Idle) { throw new Error('Cant modify splitview'); } @@ -289,7 +417,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { this.state = State.Busy; if (index < 0 || index >= this.viewItems.length) { - return; + throw new Error('Index out of bounds'); } // Remove view @@ -301,12 +429,16 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { const sashIndex = Math.max(index - 1, 0); const sashItem = this.sashItems.splice(sashIndex, 1)[0]; sashItem.disposable.dispose(); - } else { - this.lastRenderHeight = NaN, this.lastRenderTop = NaN; } this.relayout(); this.state = State.Idle; + + if (sizing && sizing.type === 'distribute') { + this.distributeViewSizes(); + } + + return viewItem.view; } moveView(from: number, to: number): void { @@ -314,36 +446,36 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { throw new Error('Cant modify splitview'); } - this.state = State.Busy; - - if (from < 0 || from >= this.viewItems.length) { - return; - } - - if (to < 0 || to >= this.viewItems.length) { - return; - } - - if (from === to) { - return; - } - - const viewItem = this.viewItems.splice(from, 1)[0]; - this.viewItems.splice(to, 0, viewItem); - - if (to + 1 < this.viewItems.length) { - this.el.insertBefore(viewItem.container, this.viewItems[to + 1].container); - } else { - this.el.appendChild(viewItem.container); - } - - this.layoutViews(); - this.state = State.Idle; + const size = this.getViewSize(from); + const view = this.removeView(from); + this.addView(view, size, to); } - private relayout(lowPriorityIndex?: number): void { + swapViews(from: number, to: number): void { + if (this.state !== State.Idle) { + throw new Error('Cant modify splitview'); + } + + if (from > to) { + return this.swapViews(to, from); + } + + const fromSize = this.getViewSize(from); + const toSize = this.getViewSize(to); + const toView = this.removeView(to); + const fromView = this.removeView(from); + + this.addView(toView, fromSize, from); + this.addView(fromView, toSize, to); + } + + private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void { const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex); + + this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex); + this.distributeEmptySpace(); + this.layoutViews(); + this.saveProportions(); } public setScrollPosition(position: number) { @@ -351,14 +483,188 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { } layout(size: number): void { - const previousSize = this.size; + const previousSize = Math.max(this.size, this.contentSize); this.size = size; this.contentSize = 0; this.lastRenderHeight = undefined; this.lastRenderTop = undefined; - this.resize(this.viewItems.length - 1, size - previousSize); + + if (!this.proportions) { + this.resize(this.viewItems.length - 1, size - previousSize); + } else { + for (let i = 0; i < this.viewItems.length; i++) { + const item = this.viewItems[i]; + item.size = clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize); + } + } + + this.distributeEmptySpace(); + this.layoutViews(); } + private saveProportions(): void { + if (this.contentSize > 0) { + this.proportions = this.viewItems.map(i => i.size / this.contentSize); + } + } + + private onSashStart({ sash, start, alt }: ISashEvent): void { + const index = firstIndex(this.sashItems, item => item.sash === sash); + + // This way, we can press Alt while we resize a sash, macOS style! + const disposable = combinedDisposable([ + domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)), + domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false)) + ]); + + const resetSashDragState = (start: number, alt: boolean) => { + const sizes = this.viewItems.map(i => i.size); + let minDelta = Number.NEGATIVE_INFINITY; + let maxDelta = Number.POSITIVE_INFINITY; + + if (this.inverseAltBehavior) { + alt = !alt; + } + + if (alt) { + // When we're using the last sash with Alt, we're resizing + // the view to the left/up, instead of right/down as usual + // Thus, we must do the inverse of the usual + const isLastSash = index === this.sashItems.length - 1; + + if (isLastSash) { + const viewItem = this.viewItems[index]; + minDelta = (viewItem.view.minimumSize - viewItem.size) / 2; + maxDelta = (viewItem.view.maximumSize - viewItem.size) / 2; + } else { + const viewItem = this.viewItems[index + 1]; + minDelta = (viewItem.size - viewItem.view.maximumSize) / 2; + maxDelta = (viewItem.size - viewItem.view.minimumSize) / 2; + } + } + + this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, disposable }; + }; + + resetSashDragState(start, alt); + } + + private onSashChange({ current }: ISashEvent): void { + const { index, start, sizes, alt, minDelta, maxDelta } = this.sashDragState; + this.sashDragState.current = current; + + const delta = current - start; + const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta); + + if (alt) { + const isLastSash = index === this.sashItems.length - 1; + const newSizes = this.viewItems.map(i => i.size); + const viewItemIndex = isLastSash ? index : index + 1; + const viewItem = this.viewItems[viewItemIndex]; + const newMinDelta = viewItem.size - viewItem.view.maximumSize; + const newMaxDelta = viewItem.size - viewItem.view.minimumSize; + const resizeIndex = isLastSash ? index - 1 : index + 1; + + this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta); + } + + this.distributeEmptySpace(); + this.layoutViews(); + } + + private onSashEnd(index: number): void { + this._onDidSashChange.fire(index); + this.sashDragState.disposable.dispose(); + this.saveProportions(); + } + + private onViewChange(item: IViewItem, size: number | undefined): void { + const index = this.viewItems.indexOf(item); + + if (index < 0 || index >= this.viewItems.length) { + return; + } + + size = typeof size === 'number' ? size : item.size; + size = clamp(size, item.view.minimumSize, item.view.maximumSize); + + if (this.inverseAltBehavior && index > 0) { + // In this case, we want the view to grow or shrink both sides equally + // so we just resize the "left" side by half and let `resize` do the clamping magic + this.resize(index - 1, Math.floor((item.size - size) / 2)); + this.distributeEmptySpace(); + this.layoutViews(); + } else { + item.size = size; + this.updateSize(item.view.id, size); + let top = item.top + item.size; + for (let i = index + 1; i < this.viewItems.length; i++) { + let currentItem = this.viewItems[i]; + this.updateTop(currentItem.view.id, top); + top += currentItem.size; + } + this.relayout(index); + } + } + + resizeView(index: number, size: number): void { + if (this.state !== State.Idle) { + throw new Error('Cant modify splitview'); + } + + this.state = State.Busy; + + if (index < 0 || index >= this.viewItems.length) { + return; + } + + const item = this.viewItems[index]; + size = Math.round(size); + size = clamp(size, item.view.minimumSize, item.view.maximumSize); + let delta = size - item.size; + + if (delta !== 0 && index < this.viewItems.length - 1) { + const downIndexes = range(index + 1, this.viewItems.length); + const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); + const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); + const deltaDown = clamp(delta, -expandDown, collapseDown); + + this.resize(index, deltaDown); + delta -= deltaDown; + } + + if (delta !== 0 && index > 0) { + const upIndexes = range(index - 1, -1); + const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); + const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); + const deltaUp = clamp(-delta, -collapseUp, expandUp); + + this.resize(index - 1, deltaUp); + } + + this.distributeEmptySpace(); + this.layoutViews(); + this.saveProportions(); + this.state = State.Idle; + } + + distributeViewSizes(): void { + const size = Math.floor(this.size / this.viewItems.length); + + for (let i = 0; i < this.viewItems.length - 1; i++) { + this.resizeView(i, size); + } + } + + getViewSize(index: number): number { + if (index < 0 || index >= this.viewItems.length) { + return -1; + } + + return this.viewItems[index].size; + } + + private render(scrollTop: number, viewHeight: number): void { let i: number; let stop: number; @@ -398,96 +704,13 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { let topItem = this.itemAtIndex(this.indexAt(renderTop)); if (topItem) { - this.el.style.top = (topItem.top - renderTop) + 'px'; + this.viewContainer.style.top = (topItem.top - renderTop) + 'px'; } this.lastRenderTop = renderTop; this.lastRenderHeight = renderBottom - renderTop; } - private onSashStart({ sash, start }: ISashEvent): void { - const index = firstIndex(this.sashItems, item => item.sash === sash); - const sizes = this.viewItems.map(i => i.size); - - // const upIndexes = range(index, -1); - // const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); - // const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - - // const downIndexes = range(index + 1, this.viewItems.length); - // const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); - // const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - - // const minDelta = -Math.min(collapseUp, expandDown); - // const maxDelta = Math.min(collapseDown, expandUp); - - this.sashDragState = { start, index, sizes }; - } - - private onSashChange({ sash, current }: ISashEvent): void { - const { index, start, sizes } = this.sashDragState; - const delta = current - start; - - this.resize(index, delta, sizes); - } - - private onViewChange(item: IViewItem, size: number | undefined): void { - const index = this.viewItems.indexOf(item); - - if (index < 0 || index >= this.viewItems.length) { - return; - } - - size = typeof size === 'number' ? size : item.size; - size = clamp(size, item.view.minimumSize, item.view.maximumSize); - item.size = size; - this.updateSize(item.view.id, size); - let top = item.top + item.size; - for (let i = index + 1; i < this.viewItems.length; i++) { - let currentItem = this.viewItems[i]; - this.updateTop(currentItem.view.id, top); - top += currentItem.size; - } - this.relayout(index); - } - - resizeView(index: number, size: number): void { - if (this.state !== State.Idle) { - throw new Error('Cant modify splitview'); - } - - this.state = State.Busy; - - if (index < 0 || index >= this.viewItems.length) { - return; - } - - const item = this.viewItems[index]; - size = Math.round(size); - size = clamp(size, item.view.minimumSize, item.view.maximumSize); - let delta = size - item.size; - - if (delta !== 0 && index < this.viewItems.length - 1) { - const downIndexes = range(index + 1, this.viewItems.length); - const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); - const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); - const deltaDown = clamp(delta, -expandDown, collapseDown); - - this.resize(index, deltaDown); - delta -= deltaDown; - } - - if (delta !== 0 && index > 0) { - const upIndexes = range(index - 1, -1); - const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); - const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); - const deltaUp = clamp(-delta, -collapseUp, expandUp); - - this.resize(index - 1, deltaUp); - } - - this.state = State.Idle; - } - // DOM changes private insertItemInDOM(item: IViewItem): boolean { @@ -503,13 +726,13 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { } if (elementAfter === null) { - this.el.appendChild(item.container); + this.viewContainer.appendChild(item.container); } else { try { - this.el.insertBefore(item.container, elementAfter); + this.viewContainer.insertBefore(item.container, elementAfter); } catch (e) { // console.warn('Failed to locate previous tree element'); - this.el.appendChild(item.container); + this.viewContainer.appendChild(item.container); } } @@ -524,85 +747,94 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { return false; } - this.el.removeChild(item.container); + this.viewContainer.removeChild(item.container); item.onRemove(); return true; } - getViewSize(index: number): number { + private resize( + index: number, + delta: number, + sizes = this.viewItems.map(i => i.size), + lowPriorityIndex?: number, + highPriorityIndex?: number, + overloadMinDelta: number = Number.NEGATIVE_INFINITY, + overloadMaxDelta: number = Number.POSITIVE_INFINITY + ): number { if (index < 0 || index >= this.viewItems.length) { - return -1; + return 0; } - return this.viewItems[index].size; + const upIndexes = range(index, -1); + const downIndexes = range(index + 1, this.viewItems.length); + + if (typeof highPriorityIndex === 'number') { + pushToStart(upIndexes, highPriorityIndex); + pushToStart(downIndexes, highPriorityIndex); + } + + if (typeof lowPriorityIndex === 'number') { + pushToEnd(upIndexes, lowPriorityIndex); + pushToEnd(downIndexes, lowPriorityIndex); + } + + const upItems = upIndexes.map(i => this.viewItems[i]); + const upSizes = upIndexes.map(i => sizes[i]); + + const downItems = downIndexes.map(i => this.viewItems[i]); + const downSizes = downIndexes.map(i => sizes[i]); + + const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.minimumSize - sizes[i]), 0); + const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); + const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); + const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.maximumSize), 0); + const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta); + const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta); + + delta = clamp(delta, minDelta, maxDelta); + + for (let i = 0, deltaUp = delta; i < upItems.length; i++) { + const item = upItems[i]; + const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize); + const viewDelta = size - upSizes[i]; + + deltaUp -= viewDelta; + item.size = size; + this.dirtyState = true; + } + + for (let i = 0, deltaDown = delta; i < downItems.length; i++) { + const item = downItems[i]; + const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize); + const viewDelta = size - downSizes[i]; + + deltaDown += viewDelta; + item.size = size; + this.dirtyState = true; + } + + return delta; } - private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size), lowPriorityIndex?: number): void { - if (index < 0 || index >= this.viewItems.length) { - return; - } - - if (delta !== 0) { - let upIndexes = range(index, -1); - let downIndexes = range(index + 1, this.viewItems.length); - - if (typeof lowPriorityIndex === 'number') { - upIndexes = pushToEnd(upIndexes, lowPriorityIndex); - downIndexes = pushToEnd(downIndexes, lowPriorityIndex); - } - - const upItems = upIndexes.map(i => this.viewItems[i]); - const upSizes = upIndexes.map(i => sizes[i]); - - const downItems = downIndexes.map(i => this.viewItems[i]); - const downSizes = downIndexes.map(i => sizes[i]); - - for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < upItems.length; i++) { - const item = upItems[i]; - const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize); - const viewDelta = size - upSizes[i]; - - deltaUp -= viewDelta; - item.size = size; - this.dirtyState = true; - } - - for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < downItems.length; i++) { - const item = downItems[i]; - const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize); - const viewDelta = size - downSizes[i]; - - deltaDown += viewDelta; - item.size = size; - this.dirtyState = true; - } - } - + private distributeEmptySpace(): void { let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); let emptyDelta = this.size - contentSize; - for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) { + for (let i = this.viewItems.length - 1; emptyDelta !== 0 && i >= 0; i--) { const item = this.viewItems[i]; const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize); const viewDelta = size - item.size; emptyDelta -= viewDelta; item.size = size; - this.dirtyState = true; } - - this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - - this.scrollable.setScrollDimensions({ - scrollHeight: this.contentSize, - height: this.size - }); - - this.layoutViews(); } private layoutViews(): void { + // Save new content size + this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); + if (this.dirtyState) { for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) { this.viewItems[i].layout(); @@ -613,27 +845,10 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { this.dirtyState = false; } - // Update sashes enablement - // let previous = false; - // const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous); - - // previous = false; - // const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous); - - // const reverseViews = [...this.viewItems].reverse(); - // previous = false; - // const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse(); - - // previous = false; - // const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse(); - - // this.sashItems.forEach((s, i) => { - // if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) { - // s.sash.enable(); - // } else { - // s.sash.disable(); - // } - // }); + this.scrollable.setScrollDimensions({ + scrollHeight: this.contentSize, + height: this.size + }); } private getSashPosition(sash: Sash): number { diff --git a/src/sql/base/browser/ui/splitview/arrow-collapse-dark.svg b/src/sql/base/browser/ui/splitview/arrow-collapse-dark.svg deleted file mode 100644 index 6f3abfce78..0000000000 --- a/src/sql/base/browser/ui/splitview/arrow-collapse-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/sql/base/browser/ui/splitview/arrow-collapse.svg b/src/sql/base/browser/ui/splitview/arrow-collapse.svg deleted file mode 100644 index 5dcb87c772..0000000000 --- a/src/sql/base/browser/ui/splitview/arrow-collapse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/sql/base/browser/ui/splitview/arrow-expand-dark.svg b/src/sql/base/browser/ui/splitview/arrow-expand-dark.svg deleted file mode 100644 index 22dfac04f1..0000000000 --- a/src/sql/base/browser/ui/splitview/arrow-expand-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/sql/base/browser/ui/splitview/arrow-expand.svg b/src/sql/base/browser/ui/splitview/arrow-expand.svg deleted file mode 100644 index e55ccd923e..0000000000 --- a/src/sql/base/browser/ui/splitview/arrow-expand.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/sql/base/browser/ui/splitview/splitview.css b/src/sql/base/browser/ui/splitview/splitview.css deleted file mode 100644 index 312f1519dc..0000000000 --- a/src/sql/base/browser/ui/splitview/splitview.css +++ /dev/null @@ -1,94 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-split-view { - position: relative; -} - -.monaco-split-view > .split-view-view { - overflow: hidden; -} - -.monaco-split-view.vertical > .split-view-view { - width: 100%; -} - -.monaco-split-view.horizontal > .split-view-view { - height: 100%; -} - -.monaco-split-view > .split-view-view > .header { - position: relative; - line-height: 22px; - font-size: 11px; - font-weight: bold; - text-transform: uppercase; - padding-left: 20px; - overflow: hidden; - display: flex; -} - -.monaco-split-view > .split-view-view > .header.hide { - display: none; -} - -/* Bold font style does not go well with CJK fonts */ -.monaco-split-view:lang(zh-Hans) > .split-view-view > .header, -.monaco-split-view:lang(zh-Hant) > .split-view-view > .header, -.monaco-split-view:lang(ja) > .split-view-view > .header, -.monaco-split-view:lang(ko) > .split-view-view > .header { font-weight: normal; } - -.monaco-split-view > .split-view-view > .header.collapsible { - cursor: pointer; -} - -.monaco-split-view > .split-view-view > .header.collapsible { - background-image: url('arrow-collapse.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -.monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) { - background-image: url('arrow-expand.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -.vs-dark .monaco-split-view > .split-view-view > .header.collapsible { - background-image: url('arrow-collapse-dark.svg'); -} - -.vs-dark .monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) { - background-image: url('arrow-expand-dark.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -/* Animation */ - -.monaco-split-view.animated > .split-view-view { - transition-duration: 0.15s; - -webkit-transition-duration: 0.15s; - -moz-transition-duration: 0.15s; - transition-timing-function: ease-out; - -webkit-transition-timing-function: ease-out; - -moz-transition-timing-function: ease-out; -} - -.monaco-split-view.vertical.animated > .split-view-view { - transition-property: height; - -webkit-transition-property: height; - -moz-transition-property: height; -} - -.monaco-split-view.horizontal.animated > .split-view-view { - transition-property: width; - -webkit-transition-property: width; - -moz-transition-property: width; -} - -.hc-black .split-view-view > .header .action-label:before { - top: 4px !important; -} \ No newline at end of file diff --git a/src/sql/base/browser/ui/splitview/splitview.ts b/src/sql/base/browser/ui/splitview/splitview.ts deleted file mode 100644 index 3d170c09d2..0000000000 --- a/src/sql/base/browser/ui/splitview/splitview.ts +++ /dev/null @@ -1,1044 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import 'vs/css!./splitview'; -import lifecycle = require('vs/base/common/lifecycle'); -import ee = require('sql/base/common/eventEmitter'); -import types = require('vs/base/common/types'); -import dom = require('vs/base/browser/dom'); -import numbers = require('vs/base/common/numbers'); -import sash = require('vs/base/browser/ui/sash/sash'); -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Event, Emitter } from 'vs/base/common/event'; -import { Color } from 'vs/base/common/color'; -import { SashState } from 'vs/base/browser/ui/sash/sash'; - -export enum Orientation { - VERTICAL, - HORIZONTAL -} - -export enum ViewSizing { - Flexible, - Fixed -} - -export interface IOptions { - orientation?: Orientation; // default Orientation.VERTICAL - canChangeOrderByDragAndDrop?: boolean; -} - -export interface ISashEvent { - start: number; - current: number; -} - -export interface IViewOptions { - sizing?: ViewSizing; - fixedSize?: number; - minimumSize?: number; -} - -export interface IView extends ee.IEventEmitter { - preferredSize: number; - size: number; - sizing: ViewSizing; - fixedSize: number; - minimumSize: number; - maximumSize: number; - draggableElement?: HTMLElement; - draggableLabel?: string; - render(container: HTMLElement, orientation: Orientation): void; - layout(size: number, orientation: Orientation): void; - focus(): void; -} - -interface IState { - start?: number; - sizes?: number[]; - up?: number[]; - down?: number[]; - maxUp?: number; - maxDown?: number; - collapses: number[]; - expands: number[]; -} - -export abstract class View extends ee.EventEmitter implements IView { - - size: number; - protected _sizing: ViewSizing; - protected _fixedSize: number; - protected _minimumSize: number; - - constructor(public preferredSize: number, opts: IViewOptions) { - super(); - - this.size = 0; - this._sizing = types.isUndefined(opts.sizing) ? ViewSizing.Flexible : opts.sizing; - this._fixedSize = types.isUndefined(opts.fixedSize) ? 22 : opts.fixedSize; - this._minimumSize = types.isUndefined(opts.minimumSize) ? 22 : opts.minimumSize; - } - - get sizing(): ViewSizing { return this._sizing; } - get fixedSize(): number { return this._fixedSize; } - get minimumSize(): number { return this.sizing === ViewSizing.Fixed ? this.fixedSize : this._minimumSize; } - get maximumSize(): number { return this.sizing === ViewSizing.Fixed ? this.fixedSize : Number.POSITIVE_INFINITY; } - - protected setFlexible(size?: number): void { - this._sizing = ViewSizing.Flexible; - this.emit('change', types.isUndefined(size) ? this._minimumSize : size); - } - - protected setFixed(size?: number): void { - this._sizing = ViewSizing.Fixed; - this._fixedSize = types.isUndefined(size) ? this._fixedSize : size; - this.emit('change', this._fixedSize); - } - - abstract render(container: HTMLElement, orientation: Orientation): void; - abstract focus(): void; - abstract layout(size: number, orientation: Orientation): void; -} - -export interface IHeaderViewOptions extends IHeaderViewStyles, IViewOptions { - headerSize?: number; -} - -export interface IHeaderViewStyles { - headerForeground?: Color; - headerBackground?: Color; - headerHighContrastBorder?: Color; -} - -const headerDefaultOpts = { - headerBackground: Color.fromHex('#808080').transparent(0.2) -}; - -export abstract class HeaderView extends View { - - private _headerSize: number; - private _showHeader: boolean; - - protected header: HTMLElement; - protected body: HTMLElement; - - private headerForeground: Color; - private headerBackground: Color; - private headerHighContrastBorder: Color; - - constructor(initialSize: number, opts: IHeaderViewOptions) { - super(initialSize, opts); - - this._headerSize = types.isUndefined(opts.headerSize) ? 22 : opts.headerSize; - this._showHeader = this._headerSize > 0; - - this.headerForeground = opts.headerForeground; - this.headerBackground = opts.headerBackground || headerDefaultOpts.headerBackground; - this.headerHighContrastBorder = opts.headerHighContrastBorder; - } - - style(styles: IHeaderViewStyles): void { - this.headerForeground = styles.headerForeground; - this.headerBackground = styles.headerBackground; - this.headerHighContrastBorder = styles.headerHighContrastBorder; - - this.applyStyles(); - } - - protected get headerSize(): number { - return this._showHeader ? this._headerSize : 0; - } - - protected applyStyles(): void { - if (this.header) { - const headerForegroundColor = this.headerForeground ? this.headerForeground.toString() : null; - const headerBackgroundColor = this.headerBackground ? this.headerBackground.toString() : null; - const headerHighContrastBorderColor = this.headerHighContrastBorder ? this.headerHighContrastBorder.toString() : null; - - this.header.style.color = headerForegroundColor; - this.header.style.backgroundColor = headerBackgroundColor; - this.header.style.borderTop = headerHighContrastBorderColor ? `1px solid ${headerHighContrastBorderColor}` : null; - } - } - - get draggableElement(): HTMLElement { return this.header; } - - render(container: HTMLElement, orientation: Orientation): void { - this.header = document.createElement('div'); - this.header.className = 'header'; - - let headerSize = this.headerSize + 'px'; - - if (orientation === Orientation.HORIZONTAL) { - this.header.style.width = headerSize; - } else { - this.header.style.height = headerSize; - } - - if (this._showHeader) { - this.renderHeader(this.header); - container.appendChild(this.header); - } - - this.body = document.createElement('div'); - this.body.className = 'body'; - - this.layoutBodyContainer(orientation); - this.renderBody(this.body); - container.appendChild(this.body); - - this.applyStyles(); - } - - showHeader(): boolean { - if (!this._showHeader) { - if (!this.body.parentElement.contains(this.header)) { - this.renderHeader(this.header); - this.body.parentElement.insertBefore(this.header, this.body); - } - dom.removeClass(this.header, 'hide'); - this._showHeader = true; - return true; - } - return false; - } - - hideHeader(): boolean { - if (this._showHeader) { - dom.addClass(this.header, 'hide'); - this._showHeader = false; - return true; - } - return false; - } - - layout(size: number, orientation: Orientation): void { - this.layoutBodyContainer(orientation); - this.layoutBody(size - this.headerSize); - } - - private layoutBodyContainer(orientation: Orientation): void { - let size = `calc(100% - ${this.headerSize}px)`; - - if (orientation === Orientation.HORIZONTAL) { - this.body.style.width = size; - } else { - this.body.style.height = size; - } - } - - dispose(): void { - this.header = null; - this.body = null; - - super.dispose(); - } - - protected abstract renderHeader(container: HTMLElement): void; - protected abstract renderBody(container: HTMLElement): void; - protected abstract layoutBody(size: number): void; -} - -export interface ICollapsibleViewOptions { - sizing: ViewSizing; - ariaHeaderLabel: string; - bodySize?: number; - initialState?: CollapsibleState; -} - -export enum CollapsibleState { - EXPANDED, - COLLAPSED -} - -export abstract class AbstractCollapsibleView extends HeaderView { - - protected state: CollapsibleState; - - private ariaHeaderLabel: string; - private headerClickListener: lifecycle.IDisposable; - private headerKeyListener: lifecycle.IDisposable; - private focusTracker: dom.IFocusTracker; - private _bodySize: number; - private _previousSize: number = null; - private readonly viewSizing: ViewSizing; - - constructor(initialSize: number | undefined, opts: ICollapsibleViewOptions) { - super(initialSize, opts); - this.viewSizing = opts.sizing; - this.ariaHeaderLabel = opts.ariaHeaderLabel; - - this.setBodySize(types.isUndefined(opts.bodySize) ? 22 : opts.bodySize); - - if (typeof this.preferredSize === 'undefined') { - this.preferredSize = this._bodySize + this.headerSize; - } - - this.changeState(types.isUndefined(opts.initialState) ? CollapsibleState.EXPANDED : opts.initialState); - } - - get previousSize(): number { - return this._previousSize; - } - - setBodySize(bodySize: number) { - this._bodySize = bodySize; - this.updateSize(); - } - - private updateSize() { - if (this.viewSizing === ViewSizing.Fixed) { - this.setFixed(this.state === CollapsibleState.EXPANDED ? this._bodySize + this.headerSize : this.headerSize); - } else { - this._minimumSize = this._bodySize + this.headerSize; - this._previousSize = !this.previousSize || this._previousSize < this._minimumSize ? this._minimumSize : this._previousSize; - if (this.state === CollapsibleState.EXPANDED) { - this.setFlexible(this._previousSize || this._minimumSize); - } else { - this._previousSize = this.size || this._minimumSize; - this.setFixed(this.headerSize); - } - } - } - - render(container: HTMLElement, orientation: Orientation): void { - super.render(container, orientation); - - dom.addClass(this.header, 'collapsible'); - dom.addClass(this.body, 'collapsible'); - - // Keyboard access - this.header.setAttribute('tabindex', '0'); - this.header.setAttribute('role', 'toolbar'); - if (this.ariaHeaderLabel) { - this.header.setAttribute('aria-label', this.ariaHeaderLabel); - } - this.header.setAttribute('aria-expanded', String(this.state === CollapsibleState.EXPANDED)); - this.headerKeyListener = dom.addDisposableListener(this.header, dom.EventType.KEY_DOWN, (e) => { - let event = new StandardKeyboardEvent(e); - let eventHandled = false; - if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space) || (event.equals(KeyCode.LeftArrow) && this.state === CollapsibleState.EXPANDED) || (event.equals(KeyCode.RightArrow) && this.state === CollapsibleState.COLLAPSED)) { - this.toggleExpansion(); - eventHandled = true; - } else if (event.equals(KeyCode.Escape)) { - this.header.blur(); - eventHandled = true; - } else if (event.equals(KeyCode.UpArrow)) { - this.emit('focusPrevious'); - eventHandled = true; - } else if (event.equals(KeyCode.DownArrow)) { - this.emit('focusNext'); - eventHandled = true; - } - - if (eventHandled) { - dom.EventHelper.stop(event, true); - } - }); - - // Mouse access - this.headerClickListener = dom.addDisposableListener(this.header, dom.EventType.CLICK, () => this.toggleExpansion()); - - // Track state of focus in header so that other components can adjust styles based on that - // (for example show or hide actions based on the state of being focused or not) - this.focusTracker = dom.trackFocus(this.header); - this.focusTracker.onDidFocus(() => { - dom.addClass(this.header, 'focused'); - }); - - this.focusTracker.onDidBlur(() => { - dom.removeClass(this.header, 'focused'); - }); - } - - focus(): void { - if (this.header) { - this.header.focus(); - } - } - - layout(size: number, orientation: Orientation): void { - this.layoutHeader(); - super.layout(size, orientation); - } - - isExpanded(): boolean { - return this.state === CollapsibleState.EXPANDED; - } - - expand(): void { - if (this.isExpanded()) { - return; - } - - this.changeState(CollapsibleState.EXPANDED); - } - - collapse(): void { - if (!this.isExpanded()) { - return; - } - - this.changeState(CollapsibleState.COLLAPSED); - } - - toggleExpansion(): void { - if (this.isExpanded()) { - this.collapse(); - } else { - this.expand(); - } - } - - private layoutHeader(): void { - if (!this.header) { - return; - } - - if (this.state === CollapsibleState.COLLAPSED) { - dom.addClass(this.header, 'collapsed'); - } else { - dom.removeClass(this.header, 'collapsed'); - } - } - - protected changeState(state: CollapsibleState): void { - this.state = state; - - if (this.header) { - this.header.setAttribute('aria-expanded', String(this.state === CollapsibleState.EXPANDED)); - } - - this.layoutHeader(); - this.updateSize(); - } - - showHeader(): boolean { - const result = super.showHeader(); - if (result) { - this.updateSize(); - } - return result; - } - - hideHeader(): boolean { - const result = super.hideHeader(); - if (result) { - this.updateSize(); - } - return result; - } - - dispose(): void { - if (this.headerClickListener) { - this.headerClickListener.dispose(); - this.headerClickListener = null; - } - - if (this.headerKeyListener) { - this.headerKeyListener.dispose(); - this.headerKeyListener = null; - } - - if (this.focusTracker) { - this.focusTracker.dispose(); - this.focusTracker = null; - } - - super.dispose(); - } -} - -class PlainView extends View { - render() { } - focus() { } - layout() { } -} - -class DeadView extends PlainView { - - constructor(view: IView) { - super(view.size, { sizing: ViewSizing.Fixed, fixedSize: 0 }); - } -} - -class VoidView extends PlainView { - - constructor() { - super(0, { sizing: ViewSizing.Fixed, minimumSize: 0, fixedSize: 0 }); - } - - setFlexible(size?: number): void { - super.setFlexible(size); - } - - setFixed(size?: number): void { - super.setFixed(size); - } -} - -function sum(arr: number[]): number { - return arr.reduce((a, b) => a + b); -} - -export interface SplitViewStyles { - dropBackground?: Color; -} - -export class SplitView extends lifecycle.Disposable implements - sash.IHorizontalSashLayoutProvider, - sash.IVerticalSashLayoutProvider { - private orientation: Orientation; - private canDragAndDrop: boolean; - private el: HTMLElement; - private size: number; - private viewElements: HTMLElement[]; - private views: IView[]; - private viewChangeListeners: lifecycle.IDisposable[]; - private viewFocusPreviousListeners: lifecycle.IDisposable[]; - private viewFocusNextListeners: lifecycle.IDisposable[]; - private viewFocusListeners: lifecycle.IDisposable[]; - private viewDnDListeners: lifecycle.IDisposable[][]; - private sashOrientation: sash.Orientation; - private sashes: sash.Sash[]; - private sashesListeners: lifecycle.IDisposable[]; - private measureContainerSize: () => number; - private layoutViewElement: (viewElement: HTMLElement, size: number) => void; - private eventWrapper: (event: sash.ISashEvent) => ISashEvent; - private animationTimeout: number; - private state: IState; - private draggedView: IView; - private dropBackground: Color; - - private _onFocus: Emitter = this._register(new Emitter()); - readonly onFocus: Event = this._onFocus.event; - - private _onDidOrderChange: Emitter = this._register(new Emitter()); - readonly onDidOrderChange: Event = this._onDidOrderChange.event; - - constructor(container: HTMLElement, options?: IOptions) { - super(); - options = options || {}; - - this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation; - this.canDragAndDrop = !!options.canChangeOrderByDragAndDrop; - - this.el = document.createElement('div'); - dom.addClass(this.el, 'monaco-split-view'); - dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal'); - container.appendChild(this.el); - - this.size = null; - this.viewElements = []; - this.views = []; - this.viewChangeListeners = []; - this.viewFocusPreviousListeners = []; - this.viewFocusNextListeners = []; - this.viewFocusListeners = []; - this.viewDnDListeners = []; - this.sashes = []; - this.sashesListeners = []; - this.animationTimeout = null; - - this.sashOrientation = this.orientation === Orientation.VERTICAL - ? sash.Orientation.HORIZONTAL - : sash.Orientation.VERTICAL; - - if (this.orientation === Orientation.VERTICAL) { - this.measureContainerSize = () => dom.getContentHeight(container); - this.layoutViewElement = (viewElement, size) => viewElement.style.height = size + 'px'; - this.eventWrapper = e => { return { start: e.startY, current: e.currentY }; }; - } else { - this.measureContainerSize = () => dom.getContentWidth(container); - this.layoutViewElement = (viewElement, size) => viewElement.style.width = size + 'px'; - this.eventWrapper = e => { return { start: e.startX, current: e.currentX }; }; - } - - // The void space exists to handle the case where all other views are fixed size - this.addView(new VoidView(), 1, 0); - } - - getViews(): T[] { - return this.views.slice(0, this.views.length - 1); - } - - addView(view: IView, initialWeight: number = 1, index = this.views.length - 1): void { - if (initialWeight <= 0) { - throw new Error('Initial weight must be a positive number.'); - } - - /** - * Reset size to null. This will layout newly added views to initial weights. - */ - this.size = null; - - let viewCount = this.views.length; - - // Create view container - let viewElement = document.createElement('div'); - dom.addClass(viewElement, 'split-view-view'); - this.viewElements.splice(index, 0, viewElement); - - // Create view - view.render(viewElement, this.orientation); - this.views.splice(index, 0, view); - - // Render view - if (index === viewCount) { - this.el.appendChild(viewElement); - } else { - this.el.insertBefore(viewElement, this.el.children.item(index)); - } - - // Listen to Drag and Drop - this.viewDnDListeners[index] = this.createDnDListeners(view, viewElement); - - // Add sash - if (this.views.length > 2) { - let s = new sash.Sash(this.el, this, { orientation: this.sashOrientation }); - this.sashes.splice(index - 1, 0, s); - this.sashesListeners.push(s.onDidStart((e) => this.onSashStart(s, this.eventWrapper(e)))); - this.sashesListeners.push(s.onDidChange((e) => this.onSashChange(s, this.eventWrapper(e)))); - } - - this.viewChangeListeners.splice(index, 0, view.addListener('change', size => this.onViewChange(view, size))); - this.onViewChange(view, view.minimumSize); - - let viewFocusTracker = dom.trackFocus(viewElement); - this.viewFocusListeners.splice(index, 0, viewFocusTracker); - viewFocusTracker.onDidFocus(() => this._onFocus.fire(view)); - - this.viewFocusPreviousListeners.splice(index, 0, view.addListener('focusPrevious', () => index > 0 && this.views[index - 1].focus())); - this.viewFocusNextListeners.splice(index, 0, view.addListener('focusNext', () => index < this.views.length && this.views[index + 1].focus())); - } - - removeView(view: IView): void { - let index = this.views.indexOf(view); - - if (index < 0) { - return; - } - - this.size = null; - let deadView = new DeadView(view); - this.views[index] = deadView; - this.onViewChange(deadView, 0); - - let sashIndex = Math.max(index - 1, 0); - if (sashIndex < this.sashes.length) { - this.sashes[sashIndex].dispose(); - this.sashes.splice(sashIndex, 1); - } - - this.viewChangeListeners[index].dispose(); - this.viewChangeListeners.splice(index, 1); - - this.viewFocusPreviousListeners[index].dispose(); - this.viewFocusPreviousListeners.splice(index, 1); - - this.viewFocusListeners[index].dispose(); - this.viewFocusListeners.splice(index, 1); - - this.viewFocusNextListeners[index].dispose(); - this.viewFocusNextListeners.splice(index, 1); - - lifecycle.dispose(this.viewDnDListeners[index]); - this.viewDnDListeners.splice(index, 1); - - this.views.splice(index, 1); - this.el.removeChild(this.viewElements[index]); - this.viewElements.splice(index, 1); - - deadView.dispose(); - view.dispose(); - } - - layout(size?: number): void { - size = size || this.measureContainerSize(); - - if (this.size === null) { - this.size = size; - this.initialLayout(); - return; - } - - size = Math.max(size, this.views.reduce((t, v) => t + v.minimumSize, 0)); - - let diff = Math.abs(this.size - size); - let up = numbers.countToArray(this.views.length - 1, -1); - - let collapses = this.views.map(v => v.size - v.minimumSize); - let expands = this.views.map(v => v.maximumSize - v.size); - - if (size < this.size) { - this.expandCollapse(Math.min(diff, sum(collapses)), collapses, expands, up, []); - } else if (size > this.size) { - this.expandCollapse(Math.min(diff, sum(expands)), collapses, expands, [], up); - } - - this.size = size; - this.layoutViews(); - } - - style(styles: SplitViewStyles): void { - this.dropBackground = styles.dropBackground; - } - - private createDnDListeners(view: IView, viewElement: HTMLElement): lifecycle.IDisposable[] { - if (!this.canDragAndDrop || view instanceof VoidView) { - return []; - } - - const disposables: lifecycle.IDisposable[] = []; - - // Allow to drag - if (view.draggableElement) { - view.draggableElement.draggable = true; - disposables.push(dom.addDisposableListener(view.draggableElement, dom.EventType.DRAG_START, (e: DragEvent) => { - e.dataTransfer.effectAllowed = 'move'; - - const dragImage = document.createElement('div'); - dragImage.className = 'monaco-tree-drag-image'; - dragImage.textContent = view.draggableLabel ? view.draggableLabel : view.draggableElement.textContent; - document.body.appendChild(dragImage); - e.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => document.body.removeChild(dragImage), 0); - - this.draggedView = view; - })); - } - - // Drag enter - let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470 - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_ENTER, (e: DragEvent) => { - if (this.draggedView && this.draggedView !== view) { - counter++; - this.updateFromDragging(view, viewElement, true); - } - })); - - // Drag leave - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_LEAVE, (e: DragEvent) => { - if (this.draggedView && this.draggedView !== view) { - counter--; - if (counter === 0) { - this.updateFromDragging(view, viewElement, false); - } - } - })); - - // Drag end - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_END, (e: DragEvent) => { - if (this.draggedView) { - counter = 0; - this.updateFromDragging(view, viewElement, false); - this.draggedView = null; - } - })); - - // Drop - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DROP, (e: DragEvent) => { - dom.EventHelper.stop(e, true); - counter = 0; - this.updateFromDragging(view, viewElement, false); - if (this.draggedView && this.draggedView !== view) { - this.move(this.views.indexOf(this.draggedView), this.views.indexOf(view)); - } - this.draggedView = null; - })); - - return disposables; - } - - private updateFromDragging(view: IView, viewElement: HTMLElement, isDragging: boolean): void { - viewElement.style.backgroundColor = isDragging && this.dropBackground ? this.dropBackground.toString() : null; - } - - private move(fromIndex: number, toIndex: number): void { - if (fromIndex < 0 || toIndex > this.views.length - 2) { - return; - } - - const [viewChangeListener] = this.viewChangeListeners.splice(fromIndex, 1); - this.viewChangeListeners.splice(toIndex, 0, viewChangeListener); - - const [viewFocusPreviousListener] = this.viewFocusPreviousListeners.splice(fromIndex, 1); - this.viewFocusPreviousListeners.splice(toIndex, 0, viewFocusPreviousListener); - - const [viewFocusListener] = this.viewFocusListeners.splice(fromIndex, 1); - this.viewFocusListeners.splice(toIndex, 0, viewFocusListener); - - const [viewFocusNextListener] = this.viewFocusNextListeners.splice(fromIndex, 1); - this.viewFocusNextListeners.splice(toIndex, 0, viewFocusNextListener); - - const [viewDnDListeners] = this.viewDnDListeners.splice(fromIndex, 1); - this.viewDnDListeners.splice(toIndex, 0, viewDnDListeners); - - const [view] = this.views.splice(fromIndex, 1); - this.views.splice(toIndex, 0, view); - - this.el.removeChild(this.viewElements[fromIndex]); - this.el.insertBefore(this.viewElements[fromIndex], this.viewElements[toIndex < fromIndex ? toIndex : toIndex + 1]); - const [viewElement] = this.viewElements.splice(fromIndex, 1); - this.viewElements.splice(toIndex, 0, viewElement); - - this.layout(); - - this._onDidOrderChange.fire(); - } - - private onSashStart(sash: sash.Sash, event: ISashEvent): void { - let i = this.sashes.indexOf(sash); - let collapses = this.views.map(v => v.size - v.minimumSize); - let expands = this.views.map(v => v.maximumSize - v.size); - - let up = numbers.countToArray(i, -1); - let down = numbers.countToArray(i + 1, this.views.length); - - let collapsesUp = up.map(i => collapses[i]); - let collapsesDown = down.map(i => collapses[i]); - let expandsUp = up.map(i => expands[i]); - let expandsDown = down.map(i => expands[i]); - - this.state = { - start: event.start, - sizes: this.views.map(v => v.size), - up: up, - down: down, - maxUp: Math.min(sum(collapsesUp), sum(expandsDown)), - maxDown: Math.min(sum(expandsUp), sum(collapsesDown)), - collapses: collapses, - expands: expands - }; - } - - private onSashChange(sash: sash.Sash, event: ISashEvent): void { - let diff = event.current - this.state.start; - - for (let i = 0; i < this.views.length; i++) { - this.views[i].size = this.views[i].preferredSize = this.state.sizes[i]; - } - - if (diff < 0) { - this.expandCollapse(Math.min(-diff, this.state.maxUp), this.state.collapses, this.state.expands, this.state.up, this.state.down); - } else { - this.expandCollapse(Math.min(diff, this.state.maxDown), this.state.collapses, this.state.expands, this.state.down, this.state.up); - } - - this.layoutViews(); - } - - // Main algorithm - private expandCollapse(collapse: number, collapses: number[], expands: number[], collapseIndexes: number[], expandIndexes: number[]): void { - let totalCollapse = collapse; - let totalExpand = totalCollapse; - - collapseIndexes.forEach(i => { - let collapse = Math.min(collapses[i], totalCollapse); - totalCollapse -= collapse; - this.views[i].size -= collapse; - }); - - expandIndexes.forEach(i => { - let expand = Math.min(expands[i], totalExpand); - totalExpand -= expand; - this.views[i].size += expand; - }); - } - - private initialLayout(): void { - let totalWeight = 0; - let fixedSize = 0; - - this.views.forEach((v, i) => { - if (v.sizing === ViewSizing.Flexible) { - totalWeight += v.preferredSize; - } else { - fixedSize += v.fixedSize; - } - }); - - let flexibleSize = this.size - fixedSize; - - this.views.forEach((v, i) => { - if (v.sizing === ViewSizing.Flexible) { - if (totalWeight === 0) { - v.size = flexibleSize; - } else { - v.size = v.preferredSize * flexibleSize / totalWeight; - } - } else { - v.size = v.fixedSize; - } - }); - - // Leftover - let index = this.getLastFlexibleViewIndex(); - if (index >= 0) { - this.views[index].size += this.size - this.views.reduce((t, v) => t + v.size, 0); - } - - // Layout - this.layoutViews(); - } - - private getLastFlexibleViewIndex(exceptIndex: number = null): number { - for (let i = this.views.length - 1; i >= 0; i--) { - if (exceptIndex === i) { - continue; - } - if (this.views[i].sizing === ViewSizing.Flexible) { - return i; - } - } - - return -1; - } - - private layoutViews(): void { - for (let i = 0; i < this.views.length; i++) { - // Layout the view elements - this.layoutViewElement(this.viewElements[i], this.views[i].size); - - // Layout the views themselves - this.views[i].layout(this.views[i].size, this.orientation); - } - - // Layout the sashes - this.sashes.forEach(s => s.layout()); - - // Update sashes enablement - let previous = false; - let collapsesDown = this.views.map(v => previous = (v.size - v.minimumSize > 0) || previous); - - previous = false; - let expandsDown = this.views.map(v => previous = (v.maximumSize - v.size > 0) || previous); - - let reverseViews = this.views.slice().reverse(); - previous = false; - let collapsesUp = reverseViews.map(v => previous = (v.size - v.minimumSize > 0) || previous).reverse(); - - previous = false; - let expandsUp = reverseViews.map(v => previous = (v.maximumSize - v.size > 0) || previous).reverse(); - - this.sashes.forEach((s, i) => { - if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) { - s.state = SashState.Enabled; - } else { - s.state = SashState.Disabled; - } - }); - } - - private onViewChange(view: IView, size: number): void { - if (view !== this.voidView) { - if (this.areAllViewsFixed()) { - this.voidView.setFlexible(); - } else { - this.voidView.setFixed(); - } - } - - if (this.size === null) { - return; - } - - if (size === view.size) { - return; - } - - this.setupAnimation(); - - let index = this.views.indexOf(view); - let diff = Math.abs(size - view.size); - let up = numbers.countToArray(index - 1, -1); - let down = numbers.countToArray(index + 1, this.views.length); - let downUp = down.concat(up); - - let collapses = this.views.map(v => Math.max(v.size - v.minimumSize, 0)); - let expands = this.views.map(v => Math.max(v.maximumSize - v.size, 0)); - - let collapse: number, collapseIndexes: number[], expandIndexes: number[]; - - if (size < view.size) { - collapse = Math.min(downUp.reduce((t, i) => t + expands[i], 0), diff); - collapseIndexes = [index]; - expandIndexes = downUp; - - } else { - collapse = Math.min(downUp.reduce((t, i) => t + collapses[i], 0), diff); - collapseIndexes = downUp; - expandIndexes = [index]; - } - - this.expandCollapse(collapse, collapses, expands, collapseIndexes, expandIndexes); - this.layoutViews(); - } - - private setupAnimation(): void { - if (types.isNumber(this.animationTimeout)) { - window.clearTimeout(this.animationTimeout); - } - - dom.addClass(this.el, 'animated'); - this.animationTimeout = window.setTimeout(() => this.clearAnimation(), 200); - } - - private clearAnimation(): void { - this.animationTimeout = null; - dom.removeClass(this.el, 'animated'); - } - - private get voidView(): VoidView { - return this.views[this.views.length - 1] as VoidView; - } - - private areAllViewsFixed(): boolean { - return this.views.every((v, i) => v.sizing === ViewSizing.Fixed || i === this.views.length - 1); - } - - getVerticalSashLeft(sash: sash.Sash): number { - return this.getSashPosition(sash); - } - - getHorizontalSashTop(sash: sash.Sash): number { - return this.getSashPosition(sash); - } - - private getSashPosition(sash: sash.Sash): number { - let index = this.sashes.indexOf(sash); - let position = 0; - - for (let i = 0; i <= index; i++) { - position += this.views[i].size; - } - - return position; - } - - dispose(): void { - if (types.isNumber(this.animationTimeout)) { - window.clearTimeout(this.animationTimeout); - } - this.orientation = null; - this.size = null; - this.viewElements.forEach(e => this.el.removeChild(e)); - this.el = null; - this.viewElements = []; - this.views = lifecycle.dispose(this.views); - this.sashes = lifecycle.dispose(this.sashes); - this.sashesListeners = lifecycle.dispose(this.sashesListeners); - this.measureContainerSize = null; - this.layoutViewElement = null; - this.eventWrapper = null; - this.state = null; - - super.dispose(); - } -} diff --git a/src/sql/base/browser/ui/table/tableView.ts b/src/sql/base/browser/ui/table/tableView.ts deleted file mode 100644 index 237d5fdbcf..0000000000 --- a/src/sql/base/browser/ui/table/tableView.ts +++ /dev/null @@ -1,148 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Table } from './table'; -import { TableDataView } from './tableDataView'; -import { View, Orientation, AbstractCollapsibleView, HeaderView, ICollapsibleViewOptions, IViewOptions, CollapsibleState } from 'sql/base/browser/ui/splitview/splitview'; -import { $ } from 'vs/base/browser/builder'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import * as DOM from 'vs/base/browser/dom'; -import * as lifecycle from 'vs/base/common/lifecycle'; - -export class TableBasicView extends View { - private _table: Table; - private _container: HTMLElement; - - constructor( - viewOpts: IViewOptions, - data?: Array | TableDataView, - columns?: Slick.Column[], - tableOpts?: Slick.GridOptions - ) { - super(undefined, viewOpts); - this._container = document.createElement('div'); - this._container.className = 'table-view'; - this._table = new Table(this._container, { dataProvider: data, columns }, tableOpts); - } - - public get table(): Table { - return this._table; - } - - render(container: HTMLElement, orientation: Orientation): void { - container.appendChild(this._container); - } - - focus(): void { - this._table.focus(); - } - - layout(size: number, orientation: Orientation): void { - this._table.layout(size, orientation); - } -} - -export class TableHeaderView extends HeaderView { - private _table: Table; - private _container: HTMLElement; - - constructor( - private _viewTitle: string, - viewOpts: IViewOptions, - data?: Array | TableDataView, - columns?: Slick.Column[], - tableOpts?: Slick.GridOptions - ) { - super(undefined, viewOpts); - this._container = document.createElement('div'); - this._container.className = 'table-view'; - this._table = new Table(this._container, { dataProvider: data, columns }, tableOpts); - } - - public get table(): Table { - return this._table; - } - - protected renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - $('span').text(this._viewTitle).appendTo(titleDiv); - } - - protected renderBody(container: HTMLElement): void { - container.appendChild(this._container); - } - - protected layoutBody(size: number): void { - this._table.layout(size, Orientation.VERTICAL); - } - - focus(): void { - this._table.focus(); - } -} - -export class TableCollapsibleView extends AbstractCollapsibleView { - private _table: Table; - private _container: HTMLElement; - private _headerTabListener: lifecycle.IDisposable; - - constructor( - private _viewTitle: string, - viewOpts: ICollapsibleViewOptions, - data?: Array | TableDataView, - columns?: Slick.Column[], - tableOpts?: Slick.GridOptions - ) { - super(undefined, viewOpts); - this._container = document.createElement('div'); - this._container.className = 'table-view'; - this._table = new Table(this._container, { dataProvider: data, columns }, tableOpts); - } - - public render(container: HTMLElement, orientation: Orientation): void { - super.render(container, orientation); - this._headerTabListener = DOM.addDisposableListener(this.header, DOM.EventType.KEY_DOWN, (e) => { - let event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Tab) && this.state === CollapsibleState.EXPANDED) { - let element = this._table.getSelectedRows(); - if (!element || element.length === 0) { - this._table.setSelectedRows([0]); - this._table.setActiveCell(0, 1); - e.stopImmediatePropagation(); - } - } - }); - } - - public dispose(): void { - if (this._headerTabListener) { - this._headerTabListener.dispose(); - this._headerTabListener = null; - } - super.dispose(); - } - - public addContainerClass(className: string) { - this._container.classList.add(className); - } - - public get table(): Table { - return this._table; - } - - protected renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - $('span').text(this._viewTitle).appendTo(titleDiv); - } - - protected renderBody(container: HTMLElement): void { - container.appendChild(this._container); - } - - protected layoutBody(size: number): void { - this._table.layout(size, Orientation.VERTICAL); - } -} diff --git a/src/sql/base/browser/ui/views/browser/media/views.css b/src/sql/base/browser/ui/views/browser/media/views.css deleted file mode 100644 index 3c099bb7d8..0000000000 --- a/src/sql/base/browser/ui/views/browser/media/views.css +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.custom-view-tree-node-item { - display: flex; - height: 22px; - line-height: 22px; -} - -.custom-view-tree-node-item > .custom-view-tree-node-item-icon { - background-size: 16px; - background-position: left center; - background-repeat: no-repeat; - padding-right: 6px; - width: 16px; - height: 22px; - -webkit-font-smoothing: antialiased; -} - -.custom-view-tree-node-item > .custom-view-tree-node-item-label { - flex: 1; - text-overflow: ellipsis; - overflow: hidden; -} \ No newline at end of file diff --git a/src/sql/base/browser/ui/views/browser/views.ts b/src/sql/base/browser/ui/views/browser/views.ts deleted file mode 100644 index bad1e0f04f..0000000000 --- a/src/sql/base/browser/ui/views/browser/views.ts +++ /dev/null @@ -1,303 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { IThemable } from 'vs/platform/theme/common/styler'; -import * as errors from 'vs/base/common/errors'; -import { $ } from 'vs/base/browser/builder'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { prepareActions } from 'vs/workbench/browser/actions'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; -import { DelayedDragHandler } from 'vs/base/browser/dnd'; -import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { AbstractCollapsibleView, CollapsibleState, IView as IBaseView, SplitView, ViewSizing } from 'sql/base/browser/ui/splitview/splitview'; - -export interface IViewOptions { - - id: string; - - name: string; - - actionRunner: IActionRunner; - - collapsed: boolean; - -} - -export interface IViewConstructorSignature { - - new(initialSize: number, options: IViewOptions, ...services: { _serviceBrand: any; }[]): IView; - -} - -export interface IView extends IBaseView, IThemable { - - id: string; - - name: string; - - getHeaderElement(): HTMLElement; - - create(): TPromise; - - setVisible(visible: boolean): TPromise; - - isVisible(): boolean; - - getActions(): IAction[]; - - getSecondaryActions(): IAction[]; - - getActionItem(action: IAction): IActionItem; - - getActionsContext(): any; - - showHeader(): boolean; - - hideHeader(): boolean; - - focusBody(): void; - - isExpanded(): boolean; - - expand(): void; - - collapse(): void; - - getOptimalWidth(): number; - - shutdown(): void; -} - -export interface ICollapsibleViewOptions extends IViewOptions { - - ariaHeaderLabel?: string; - - sizing: ViewSizing; - - initialBodySize?: number; - -} - -export abstract class CollapsibleView extends AbstractCollapsibleView implements IView { - - readonly id: string; - readonly name: string; - - protected treeContainer: HTMLElement; - protected tree: ITree; - protected toDispose: IDisposable[]; - protected toolBar: ToolBar; - protected actionRunner: IActionRunner; - protected isDisposed: boolean; - - private _isVisible: boolean; - - private dragHandler: DelayedDragHandler; - - constructor( - initialSize: number, - options: ICollapsibleViewOptions, - protected keybindingService: IKeybindingService, - protected contextMenuService: IContextMenuService - ) { - super(initialSize, { - ariaHeaderLabel: options.ariaHeaderLabel, - sizing: options.sizing, - bodySize: options.initialBodySize ? options.initialBodySize : 4 * 22, - initialState: options.collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED, - }); - - this.id = options.id; - this.name = options.name; - this.actionRunner = options.actionRunner; - this.toDispose = []; - } - - protected changeState(state: CollapsibleState): void { - this.updateTreeVisibility(this.tree, state === CollapsibleState.EXPANDED); - - super.changeState(state); - } - - get draggableLabel(): string { return this.name; } - - public create(): TPromise { - return TPromise.as(null); - } - - getHeaderElement(): HTMLElement { - return this.header; - } - - public renderHeader(container: HTMLElement): void { - - // Tool bar - this.toolBar = new ToolBar($('div.actions').appendTo(container).getHTMLElement(), this.contextMenuService, { - orientation: ActionsOrientation.HORIZONTAL, - actionItemProvider: (action) => this.getActionItem(action), - ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.name), - getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id) - }); - this.toolBar.actionRunner = this.actionRunner; - this.updateActions(); - - // Expand on drag over - this.dragHandler = new DelayedDragHandler(container, () => { - if (!this.isExpanded()) { - this.expand(); - } - }); - } - - protected updateActions(): void { - this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); - this.toolBar.context = this.getActionsContext(); - } - - protected renderViewTree(container: HTMLElement): HTMLElement { - const treeContainer = document.createElement('div'); - container.appendChild(treeContainer); - - return treeContainer; - } - - public getViewer(): ITree { - return this.tree; - } - - public isVisible(): boolean { - return this._isVisible; - } - - public setVisible(visible: boolean): TPromise { - if (this._isVisible !== visible) { - this._isVisible = visible; - this.updateTreeVisibility(this.tree, visible && this.state === CollapsibleState.EXPANDED); - } - - return TPromise.as(null); - } - - public focusBody(): void { - this.focusTree(); - } - - protected reveal(element: any, relativeTop?: number): TPromise { - if (!this.tree) { - return TPromise.as(null); // return early if viewlet has not yet been created - } - - return this.tree.reveal(element, relativeTop); - } - - public layoutBody(size: number): void { - if (this.tree) { - this.treeContainer.style.height = size + 'px'; - this.tree.layout(size); - } - } - - public getActions(): IAction[] { - return []; - } - - public getSecondaryActions(): IAction[] { - return []; - } - - public getActionItem(action: IAction): IActionItem { - return null; - } - - public getActionsContext(): any { - return undefined; - } - - public shutdown(): void { - // Subclass to implement - } - - public getOptimalWidth(): number { - return 0; - } - - public dispose(): void { - this.isDisposed = true; - this.treeContainer = null; - - if (this.tree) { - this.tree.dispose(); - } - - if (this.dragHandler) { - this.dragHandler.dispose(); - } - - this.toDispose = dispose(this.toDispose); - - if (this.toolBar) { - this.toolBar.dispose(); - } - - super.dispose(); - } - - private updateTreeVisibility(tree: ITree, isVisible: boolean): void { - if (!tree) { - return; - } - - if (isVisible) { - $(tree.getHTMLElement()).show(); - } else { - $(tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it - } - - if (isVisible) { - tree.onVisible(); - } else { - tree.onHidden(); - } - } - - private focusTree(): void { - if (!this.tree) { - return; // return early if viewlet has not yet been created - } - - // Make sure the current selected element is revealed - const selection = this.tree.getSelection(); - if (selection.length > 0) { - this.reveal(selection[0], 0.5).done(null, errors.onUnexpectedError); - } - - // Pass Focus to Viewer - this.tree.domFocus(); - } -} - -export interface IViewletViewOptions extends IViewOptions { - - viewletSettings: object; - -} - -export interface IViewState { - - collapsed: boolean; - - size: number | undefined; - - isHidden: boolean; - - order: number; - -} diff --git a/src/sql/base/browser/ui/views/common/views.ts b/src/sql/base/browser/ui/views/common/views.ts deleted file mode 100644 index 1707780b94..0000000000 --- a/src/sql/base/browser/ui/views/common/views.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TPromise } from 'vs/base/common/winjs.base'; -import { Event } from 'vs/base/common/event'; -import { Command } from 'vs/editor/common/modes'; - -export type TreeViewItemHandleArg = { - $treeViewId: string, - $treeItemHandle: number -}; - -export enum TreeItemCollapsibleState { - None = 0, - Collapsed = 1, - Expanded = 2 -} - -export interface ITreeItem { - - handle: number; - - label: string; - - icon?: string; - - iconDark?: string; - - contextValue?: string; - - command?: Command; - - children?: ITreeItem[]; - - collapsibleState?: TreeItemCollapsibleState; -} - -export interface ITreeViewDataProvider { - - onDidChange: Event; - - onDispose: Event; - - getElements(): TPromise; - - getChildren(element: ITreeItem): TPromise; - -} \ No newline at end of file diff --git a/src/sql/common/theme/styler.ts b/src/sql/common/theme/styler.ts index d0a7936576..473d6cf741 100644 --- a/src/sql/common/theme/styler.ts +++ b/src/sql/common/theme/styler.ts @@ -11,7 +11,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import * as cr from 'vs/platform/theme/common/colorRegistry'; import { IThemable, attachStyler } from 'vs/platform/theme/common/styler'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; +import { IPanelColors } from 'vs/workbench/browser/parts/views/panelViewlet'; export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?: { @@ -262,3 +263,12 @@ export function attachCheckboxStyler(widget: IThemable, themeService: IThemeServ disabledCheckboxForeground: (style && style.disabledCheckboxForeground) || sqlcolors.disabledCheckboxForeground }, widget); } + +export function attachPanelStyler(widget: IThemable, themeService: IThemeService) { + return attachStyler(themeService, { + headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND, + headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND, + // headerHighContrastBorder: index === 0 ? null : contrastBorder, + dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND + }, widget); +} diff --git a/src/sql/parts/accountManagement/accountDialog/accountDialog.ts b/src/sql/parts/accountManagement/accountDialog/accountDialog.ts index 193907fd44..1f4e4bd789 100644 --- a/src/sql/parts/accountManagement/accountDialog/accountDialog.ts +++ b/src/sql/parts/accountManagement/accountDialog/accountDialog.ts @@ -8,35 +8,87 @@ import 'vs/css!./media/accountDialog'; import 'vs/css!sql/parts/accountManagement/common/media/accountActions'; import * as DOM from 'vs/base/browser/dom'; -import { SplitView } from 'sql/base/browser/ui/splitview/splitview'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IListService, ListService } from 'vs/platform/list/browser/listService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachListStyler } from 'vs/platform/theme/common/styler'; -import { ActionRunner } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import * as TelemetryKeys from 'sql/common/telemetryKeys'; +import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { values } from 'vs/base/common/map'; import * as sqlops from 'sqlops'; + import { Button } from 'sql/base/browser/ui/button/button'; import { Modal } from 'sql/base/browser/ui/modal/modal'; -import { attachModalDialogStyler, attachButtonStyler } from 'sql/common/theme/styler'; +import { attachModalDialogStyler, attachButtonStyler, attachPanelStyler } from 'sql/common/theme/styler'; import { AccountViewModel } from 'sql/parts/accountManagement/accountDialog/accountViewModel'; import { AddAccountAction } from 'sql/parts/accountManagement/common/accountActions'; import { AccountListRenderer, AccountListDelegate } from 'sql/parts/accountManagement/common/accountListRenderer'; import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes'; -import { FixedListView } from 'sql/platform/views/fixedListView'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import * as TelemetryKeys from 'sql/common/telemetryKeys'; + +class AccountPanel extends ViewletPanel { + public index: number; + private accountList: List; + + constructor( + private options: IViewletPanelOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService private instantiationService: IInstantiationService, + @IThemeService private themeService: IThemeService + ) { + super(options, keybindingService, contextMenuService, configurationService); + } + + protected renderBody(container: HTMLElement): void { + this.accountList = new List(container, new AccountListDelegate(AccountDialog.ACCOUNTLIST_HEIGHT), [this.instantiationService.createInstance(AccountListRenderer)]); + this.disposables.push(attachListStyler(this.accountList, this.themeService)); + } + + protected layoutBody(size: number): void { + if (this.accountList) { + this.accountList.layout(size); + } + } + + public get length(): number { + return this.accountList.length; + } + + public focus() { + this.accountList.domFocus(); + } + + public updateAccounts(accounts: sqlops.Account[]) { + this.accountList.splice(0, this.accountList.length, accounts); + } + + public setSelection(indexes: number[]) { + this.accountList.setSelection(indexes); + } + + public getActions(): IAction[] { + return [this.instantiationService.createInstance( + AddAccountAction, + this.options.id + )]; + } +} export interface IProviderViewUiComponent { - view: FixedListView; + view: AccountPanel; addAccountAction: AddAccountAction; } @@ -46,13 +98,10 @@ export class AccountDialog extends Modal { public viewModel: AccountViewModel; // MEMBER VARIABLES //////////////////////////////////////////////////// - private _providerViews: { [providerId: string]: IProviderViewUiComponent } = {}; + private _providerViewsMap = new Map(); private _closeButton: Button; private _addAccountButton: Button; - private _delegate: AccountListDelegate; - private _accountRenderer: AccountListRenderer; - private _actionRunner: ActionRunner; private _splitView: SplitView; private _container: HTMLElement; private _splitViewContainer: HTMLElement; @@ -68,10 +117,10 @@ export class AccountDialog extends Modal { constructor( @IPartService partService: IPartService, @IThemeService themeService: IThemeService, - @IListService private _listService: IListService, @IInstantiationService private _instantiationService: IInstantiationService, @IContextMenuService private _contextMenuService: IContextMenuService, @IKeybindingService private _keybindingService: IKeybindingService, + @IConfigurationService private _configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService clipboardService: IClipboardService @@ -86,11 +135,6 @@ export class AccountDialog extends Modal { contextKeyService, { hasSpinner: true } ); - let self = this; - - this._delegate = new AccountListDelegate(AccountDialog.ACCOUNTLIST_HEIGHT); - this._accountRenderer = this._instantiationService.createInstance(AccountListRenderer); - this._actionRunner = new ActionRunner(); // Setup the event emitters this._onAddAccountErrorEmitter = new Emitter(); @@ -98,28 +142,25 @@ export class AccountDialog extends Modal { // Create the view model and wire up the events this.viewModel = this._instantiationService.createInstance(AccountViewModel); - this.viewModel.addProviderEvent(arg => { self.addProvider(arg); }); - this.viewModel.removeProviderEvent(arg => { self.removeProvider(arg); }); - this.viewModel.updateAccountListEvent(arg => { self.updateProviderAccounts(arg); }); + this.viewModel.addProviderEvent(arg => { this.addProvider(arg); }); + this.viewModel.removeProviderEvent(arg => { this.removeProvider(arg); }); + this.viewModel.updateAccountListEvent(arg => { this.updateProviderAccounts(arg); }); // Load the initial contents of the view model this.viewModel.initialize() .then(addedProviders => { for (let addedProvider of addedProviders) { - self.addProvider(addedProvider); + this.addProvider(addedProvider); } }); } // MODAL OVERRIDE METHODS ////////////////////////////////////////////// protected layout(height?: number): void { - // Ignore height as it's a subcomponent being laid out this._splitView.layout(DOM.getContentHeight(this._container)); } public render() { - let self = this; - super.render(); attachModalDialogStyler(this, this._themeService); this._closeButton = this.addFooterButton(localize('accountDialog.close', 'Close'), () => this.close()); @@ -128,7 +169,7 @@ export class AccountDialog extends Modal { protected renderBody(container: HTMLElement) { this._container = container; - this._splitViewContainer = DOM.$('div.account-view'); + this._splitViewContainer = DOM.$('div.account-view.monaco-panel-view'); DOM.append(container, this._splitViewContainer); this._splitView = new SplitView(this._splitViewContainer); @@ -143,7 +184,7 @@ export class AccountDialog extends Modal { this._addAccountButton = new Button(buttonSection); this._addAccountButton.label = localize('accountDialog.addConnection', 'Add an account'); this._register(this._addAccountButton.onDidClick(() => { - (Object.values(this._providerViews)[0]).addAccountAction.run(); + (values(this._providerViewsMap)[0]).addAccountAction.run(); })); DOM.append(container, this._noaccountViewContainer); @@ -189,20 +230,19 @@ export class AccountDialog extends Modal { private showSplitView() { this._splitViewContainer.hidden = false; this._noaccountViewContainer.hidden = true; - let views = this._splitView.getViews(); - if (views && views.length > 0) { - let firstView = views[0]; - if (firstView instanceof FixedListView) { - firstView.list.setSelection([0]); - firstView.list.domFocus(); + if (values(this._providerViewsMap).length > 0) { + let firstView = values(this._providerViewsMap)[0]; + if (firstView instanceof AccountPanel) { + firstView.setSelection([0]); + firstView.focus(); } } } private isEmptyLinkedAccount(): boolean { - for (var providerId in this._providerViews) { - var listView = this._providerViews[providerId].view; - if (listView && listView.list.length > 0) { + for (let provider of values(this._providerViewsMap)) { + let listView = provider.view; + if (listView && listView.length > 0) { return false; } } @@ -211,23 +251,21 @@ export class AccountDialog extends Modal { public dispose(): void { super.dispose(); - for (let key in this._providerViews) { - if (this._providerViews[key].addAccountAction) { - this._providerViews[key].addAccountAction.dispose(); + for (let provider of values(this._providerViewsMap)) { + if (provider.addAccountAction) { + provider.addAccountAction.dispose(); } - if (this._providerViews[key].view) { - this._providerViews[key].view.dispose(); + if (provider.view) { + provider.view.dispose(); } - delete this._providerViews[key]; } } // PRIVATE HELPERS ///////////////////////////////////////////////////// private addProvider(newProvider: AccountProviderAddedEventParams) { - let self = this; // Skip adding the provider if it already exists - if (this._providerViews[newProvider.addedProvider.id]) { + if (this._providerViewsMap.get(newProvider.addedProvider.id)) { return; } @@ -237,37 +275,35 @@ export class AccountDialog extends Modal { AddAccountAction, newProvider.addedProvider.id ); - addAccountAction.addAccountCompleteEvent(() => { self.hideSpinner(); }); - addAccountAction.addAccountErrorEvent(msg => { self._onAddAccountErrorEmitter.fire(msg); }); - addAccountAction.addAccountStartEvent(() => { self.showSpinner(); }); + addAccountAction.addAccountCompleteEvent(() => { this.hideSpinner(); }); + addAccountAction.addAccountErrorEvent(msg => { this._onAddAccountErrorEmitter.fire(msg); }); + addAccountAction.addAccountStartEvent(() => { this.showSpinner(); }); - // Create a fixed list view for the account provider - let providerViewContainer = DOM.$('.provider-view'); - let accountList = new List(providerViewContainer, this._delegate, [this._accountRenderer]); - let providerView = new FixedListView( - undefined, - false, - newProvider.addedProvider.displayName, - accountList, - providerViewContainer, - 22, - [addAccountAction], - this._actionRunner, - this._contextMenuService, + let providerView = new AccountPanel( + { + id: newProvider.addedProvider.id, + title: newProvider.addedProvider.displayName, + ariaHeaderLabel: newProvider.addedProvider.displayName + }, this._keybindingService, + this._contextMenuService, + this._configurationService, + this._instantiationService, this._themeService ); - // Append the list view to the split view - this._splitView.addView(providerView); - this._register(attachListStyler(accountList, this._themeService)); + attachPanelStyler(providerView, this._themeService); + + const insertIndex = this._splitView.length; + // Append the list view to the split view + this._splitView.addView(providerView, Sizing.Distribute, insertIndex); + providerView.render(); + providerView.index = insertIndex; - let listService = this._listService; - this._register(listService.register(accountList)); this._splitView.layout(DOM.getContentHeight(this._container)); // Set the initial items of the list - providerView.updateList(newProvider.initialAccounts); + providerView.updateAccounts(newProvider.initialAccounts); if (newProvider.initialAccounts.length > 0 && this._splitViewContainer.hidden) { this.showSplitView(); @@ -276,31 +312,31 @@ export class AccountDialog extends Modal { this.layout(); // Store the view for the provider and action - this._providerViews[newProvider.addedProvider.id] = { view: providerView, addAccountAction: addAccountAction }; + this._providerViewsMap.set(newProvider.addedProvider.id, { view: providerView, addAccountAction: addAccountAction }); } private removeProvider(removedProvider: sqlops.AccountProviderMetadata) { // Skip removing the provider if it doesn't exist - let providerView = this._providerViews[removedProvider.id]; + let providerView = this._providerViewsMap.get(removedProvider.id); if (!providerView || !providerView.view) { return; } // Remove the list view from the split view - this._splitView.removeView(providerView.view); + this._splitView.removeView(providerView.view.index); this._splitView.layout(DOM.getContentHeight(this._container)); // Remove the list view from our internal map - delete this._providerViews[removedProvider.id]; + this._providerViewsMap.delete(removedProvider.id); this.layout(); } private updateProviderAccounts(args: UpdateAccountListEventParams) { - let providerMapping = this._providerViews[args.providerId]; + let providerMapping = this._providerViewsMap.get(args.providerId); if (!providerMapping || !providerMapping.view) { return; } - providerMapping.view.updateList(args.accountList); + providerMapping.view.updateAccounts(args.accountList); if (args.accountList.length > 0 && this._splitViewContainer.hidden) { this.showSplitView(); diff --git a/src/sql/parts/connection/connectionDialog/advancedPropertiesController.ts b/src/sql/parts/connection/connectionDialog/advancedPropertiesController.ts index 3ee33eac9f..95cd796886 100644 --- a/src/sql/parts/connection/connectionDialog/advancedPropertiesController.ts +++ b/src/sql/parts/connection/connectionDialog/advancedPropertiesController.ts @@ -13,8 +13,6 @@ import { localize } from 'vs/nls'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; export class AdvancedPropertiesController { - private _container: HTMLElement; - private _advancedDialog: OptionsDialog; private _options: { [name: string]: any }; @@ -30,7 +28,6 @@ export class AdvancedPropertiesController { public showDialog(providerOptions: sqlops.ConnectionOption[], container: HTMLElement, options: { [name: string]: any }): void { this._options = options; - this._container = container; var serviceOptions = providerOptions.map(option => AdvancedPropertiesController.connectionOptionToServiceOption(option)); this.advancedDialog.open(serviceOptions, this._options); } @@ -38,8 +35,7 @@ export class AdvancedPropertiesController { public get advancedDialog() { if (!this._advancedDialog) { this._advancedDialog = this._instantiationService.createInstance( - OptionsDialog, localize('connectionAdvancedProperties', 'Advanced Properties'), TelemetryKeys.ConnectionAdvancedProperties, { hasBackButton: true }); - this._advancedDialog.cancelLabel = localize('advancedProperties.discard', 'Discard'); + OptionsDialog, localize('connectionAdvancedProperties', 'Advanced Properties'), TelemetryKeys.ConnectionAdvancedProperties, { hasBackButton: true, cancelLabel: localize('advancedProperties.discard', 'Discard') }); this._advancedDialog.onCloseEvent(() => this._onCloseAdvancedProperties()); this._advancedDialog.onOk(() => this.handleOnOk()); this._advancedDialog.render(); diff --git a/src/sql/parts/dashboard/newDashboardTabDialog/newDashboardTabDialog.ts b/src/sql/parts/dashboard/newDashboardTabDialog/newDashboardTabDialog.ts index 311c54b496..c9ba15b2c6 100644 --- a/src/sql/parts/dashboard/newDashboardTabDialog/newDashboardTabDialog.ts +++ b/src/sql/parts/dashboard/newDashboardTabDialog/newDashboardTabDialog.ts @@ -27,9 +27,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Button } from 'sql/base/browser/ui/button/button'; import { Modal } from 'sql/base/browser/ui/modal/modal'; import { attachModalDialogStyler, attachButtonStyler } from 'sql/common/theme/styler'; -import { FixedListView } from 'sql/platform/views/fixedListView'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; -import { Orientation } from 'sql/base/browser/ui/splitview/splitview'; import { NewDashboardTabViewModel, IDashboardUITab } from 'sql/parts/dashboard/newDashboardTabDialog/newDashboardTabViewModel'; import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; @@ -104,8 +102,6 @@ export class NewDashboardTabDialog extends Modal { private _addNewTabButton: Button; private _cancelButton: Button; private _extensionList: List; - private _extensionTabView: FixedListView; - private _container: HTMLElement; private _extensionViewContainer: HTMLElement; private _noExtensionViewContainer: HTMLElement; @@ -121,10 +117,6 @@ export class NewDashboardTabDialog extends Modal { constructor( @IPartService partService: IPartService, @IThemeService themeService: IThemeService, - @IListService private _listService: IListService, - @IInstantiationService private _instantiationService: IInstantiationService, - @IContextMenuService private _contextMenuService: IContextMenuService, - @IKeybindingService private _keybindingService: IKeybindingService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService clipboardService: IClipboardService @@ -150,7 +142,7 @@ export class NewDashboardTabDialog extends Modal { // MODAL OVERRIDE METHODS ////////////////////////////////////////////// protected layout(height?: number): void { - // Nothing currently laid out in this class + this._extensionList.layout(height); } public render() { @@ -163,7 +155,6 @@ export class NewDashboardTabDialog extends Modal { } protected renderBody(container: HTMLElement) { - this._container = container; this._extensionViewContainer = DOM.$('div.extension-view'); DOM.append(container, this._extensionViewContainer); @@ -182,19 +173,6 @@ export class NewDashboardTabDialog extends Modal { let delegate = new ExtensionListDelegate(NewDashboardTabDialog.EXTENSIONLIST_HEIGHT); let extensionTabRenderer = new ExtensionListRenderer(); this._extensionList = new List(extensionTabViewContainer, delegate, [extensionTabRenderer]); - this._extensionTabView = new FixedListView( - undefined, - false, - localize('allFeatures', 'All features'), - this._extensionList, - extensionTabViewContainer, - 22, - [], - undefined, - this._contextMenuService, - this._keybindingService, - this._themeService - ); this._extensionList.onMouseDblClick(e => this.onAccept()); this._extensionList.onKeyDown(e => { @@ -206,13 +184,9 @@ export class NewDashboardTabDialog extends Modal { } }); - this._extensionTabView.render(container, Orientation.VERTICAL); - this._extensionTabView.hideHeader(); + DOM.append(container, extensionTabViewContainer); this._register(attachListStyler(this._extensionList, this._themeService)); - - let listService = this._listService; - this._register(listService.register(this._extensionList)); } private registerListeners(): void { @@ -252,7 +226,7 @@ export class NewDashboardTabDialog extends Modal { } private onUpdateTabList(tabs: IDashboardUITab[]) { - this._extensionTabView.updateList(tabs); + this._extensionList.splice(0, this._extensionList.length, tabs); this.layout(); if (this._extensionList.length > 0) { this._extensionViewContainer.hidden = false; diff --git a/src/sql/parts/disasterRecovery/backup/backup.component.ts b/src/sql/parts/disasterRecovery/backup/backup.component.ts index c6d5da6153..fbdd8ca4c8 100644 --- a/src/sql/parts/disasterRecovery/backup/backup.component.ts +++ b/src/sql/parts/disasterRecovery/backup/backup.component.ts @@ -13,27 +13,25 @@ import { ListBox } from 'sql/base/browser/ui/listBox/listBox'; import { ModalFooterStyle } from 'sql/base/browser/ui/modal/modal'; import { CategoryView } from 'sql/base/browser/ui/modal/optionsDialog'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; -import { SplitView } from 'sql/base/browser/ui/splitview/splitview'; import { attachButtonStyler, attachListBoxStyler, attachInputBoxStyler, attachSelectBoxStyler, attachCheckboxStyler } from 'sql/common/theme/styler'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import * as BackupConstants from 'sql/parts/disasterRecovery/backup/constants'; import { IBackupService, IBackupUiService, TaskExecutionMode } from 'sql/parts/disasterRecovery/backup/common/backupService'; import FileValidationConstants = require('sql/parts/fileBrowser/common/fileValidationServiceConstants'); -import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams'; import { IFileBrowserDialogController } from 'sql/parts/fileBrowser/common/interfaces'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; +import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview'; import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import * as lifecycle from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; import * as types from 'vs/base/common/types'; import * as strings from 'vs/base/common/strings'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export const BACKUP_SELECTOR: string = 'backup-component'; @@ -207,7 +205,8 @@ export class BackupComponent { @Inject(IBackupUiService) private _backupUiService: IBackupUiService, @Inject(IBackupService) private _backupService: IBackupService, @Inject(IClipboardService) private clipboardService: IClipboardService, - @Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService + @Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService, + @Inject(IInstantiationService) private instantiationService: IInstantiationService ) { this._backupUiService.onShowBackupEvent((param) => this.onGetBackupConfigInfo(param)); } @@ -338,10 +337,10 @@ export class BackupComponent { ngAfterViewInit() { // Set category view for advanced options. This should be defined in ngAfterViewInit so that it correctly calculates the text height after data binding. - var splitview = new SplitView(this.advancedOptionElement.nativeElement); + var splitview = new ScrollableSplitView(this.advancedOptionElement.nativeElement); var advancedBodySize = DOM.getTotalHeight(this.advancedOptionBodyElement.nativeElement); - var categoryView = new CategoryView(LocalizedStrings.ADVANCED_CONFIGURATION, this.advancedOptionBodyElement.nativeElement, true, advancedBodySize, this._advancedHeaderSize); - splitview.addView(categoryView); + var categoryView = this.instantiationService.createInstance(CategoryView, this.advancedOptionBodyElement.nativeElement, advancedBodySize, { title: LocalizedStrings.ADVANCED_CONFIGURATION, id: LocalizedStrings.ADVANCED_CONFIGURATION, ariaHeaderLabel: LocalizedStrings.ADVANCED_CONFIGURATION }); + splitview.addView(categoryView, 0); splitview.layout(advancedBodySize + this._advancedHeaderSize); this._backupUiService.onShowBackupDialog(); diff --git a/src/sql/parts/insights/browser/insightsDialogView.ts b/src/sql/parts/insights/browser/insightsDialogView.ts index 3800d84952..34e4b69525 100644 --- a/src/sql/parts/insights/browser/insightsDialogView.ts +++ b/src/sql/parts/insights/browser/insightsDialogView.ts @@ -8,25 +8,25 @@ import { Button } from 'sql/base/browser/ui/button/button'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { Modal } from 'sql/base/browser/ui/modal/modal'; import { IInsightsConfigDetails } from 'sql/parts/dashboard/widgets/insights/interfaces'; -import { attachButtonStyler, attachModalDialogStyler, attachTableStyler } from 'sql/common/theme/styler'; +import { attachButtonStyler, attachModalDialogStyler, attachTableStyler, attachPanelStyler } from 'sql/common/theme/styler'; import { TaskRegistry } from 'sql/platform/tasks/common/tasks'; import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; import { IInsightsDialogModel, ListResource, IInsightDialogActionContext, insertValueRegex } from 'sql/parts/insights/common/interfaces'; -import { TableCollapsibleView } from 'sql/base/browser/ui/table/tableView'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin'; import { error } from 'sql/base/common/log'; import { Table } from 'sql/base/browser/ui/table/table'; import { CopyInsightDialogSelectionAction } from 'sql/parts/insights/common/insightDialogActions'; -import { SplitView, ViewSizing } from 'sql/base/browser/ui/splitview/splitview'; +import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; +import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import * as DOM from 'vs/base/browser/dom'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IListService } from 'vs/platform/list/browser/listService'; import * as nls from 'vs/nls'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IAction } from 'vs/base/common/actions'; @@ -38,12 +38,45 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { MenuRegistry, ExecuteCommandAction } from 'vs/platform/actions/common/actions'; -import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; -import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const labelDisplay = nls.localize("insights.item", "Item"); const valueDisplay = nls.localize("insights.value", "Value"); +class InsightTableView extends ViewletPanel { + private _table: Table; + public get table(): Table { + return this._table; + } + + constructor( + private columns: Slick.Column[], + private data: IDisposableDataProvider | Array, + private tableOptions: Slick.GridOptions, + options: IViewletPanelOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService + ) { + super(options, keybindingService, contextMenuService, configurationService); + } + + protected renderBody(container: HTMLElement): void { + this._table = new Table(container, { + columns: this.columns, + dataProvider: this.data + }, this.tableOptions); + } + + protected layoutBody(size: number): void { + this._table.layout(size, Orientation.VERTICAL); + } + +} + function stateFormatter(row: number, cell: number, value: any, columnDef: Slick.Column, resource: ListResource): string { // template const icon = DOM.$('span.icon-span'); @@ -126,7 +159,6 @@ export class InsightsDialogView extends Modal { private _model: IInsightsDialogModel, @IInstantiationService private _instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IListService private _listService: IListService, @IPartService partService: IPartService, @IContextMenuService private _contextMenuService: IContextMenuService, @ITelemetryService telemetryService: ITelemetryService, @@ -167,6 +199,7 @@ export class InsightsDialogView extends Modal { protected renderBody(container: HTMLElement) { this._container = container; + container.classList.add('monaco-panel-view'); this._splitView = new SplitView(container); @@ -175,11 +208,14 @@ export class InsightsDialogView extends Modal { this._topTableData = new TableDataView(); this._bottomTableData = new TableDataView(); - let topTableView = new TableCollapsibleView(itemsHeaderTitle, { sizing: ViewSizing.Flexible, ariaHeaderLabel: itemsHeaderTitle }, this._topTableData, this._topColumns, { forceFitColumns: true }); + let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle, ariaHeaderLabel: itemsHeaderTitle }) as InsightTableView; + topTableView.render(); + attachPanelStyler(topTableView, this._themeService); this._topTable = topTableView.table; - topTableView.addContainerClass('insights'); this._topTable.setSelectionModel(new RowSelectionModel()); - let bottomTableView = new TableCollapsibleView(itemsDetailHeaderTitle, { sizing: ViewSizing.Flexible, ariaHeaderLabel: itemsDetailHeaderTitle }, this._bottomTableData, this._bottomColumns, { forceFitColumns: true }); + let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle, ariaHeaderLabel: itemsDetailHeaderTitle }) as InsightTableView; + bottomTableView.render(); + attachPanelStyler(bottomTableView, this._themeService); this._bottomTable = bottomTableView.table; this._bottomTable.setSelectionModel(new RowSelectionModel()); @@ -193,12 +229,9 @@ export class InsightsDialogView extends Modal { this._bottomTableData.clear(); this._bottomTableData.push(resourceArray); - // this table view has to be collapsed and expanded - // because the initial expand doesn't have the - // loaded data if (bottomTableView.isExpanded()) { - bottomTableView.collapse(); - bottomTableView.expand(); + bottomTableView.setExpanded(false); + bottomTableView.setExpanded(true); } this._enableTaskButtons(true); } else { @@ -224,8 +257,8 @@ export class InsightsDialogView extends Modal { }); })); - this._splitView.addView(topTableView); - this._splitView.addView(bottomTableView); + this._splitView.addView(topTableView, Sizing.Distribute); + this._splitView.addView(bottomTableView, Sizing.Distribute); this._register(attachTableStyler(this._topTable, this._themeService)); this._register(attachTableStyler(this._bottomTable, this._themeService)); @@ -344,7 +377,6 @@ export class InsightsDialogView extends Modal { this.hide(); dispose(this._taskButtonDisposables); this._taskButtonDisposables = []; - this.dispose(); } protected onClose(e: StandardKeyboardEvent) { diff --git a/src/sql/parts/insights/node/insightsDialogController.ts b/src/sql/parts/insights/node/insightsDialogController.ts index 92d0adafad..a8324859fb 100644 --- a/src/sql/parts/insights/node/insightsDialogController.ts +++ b/src/sql/parts/insights/node/insightsDialogController.ts @@ -129,7 +129,7 @@ export class InsightsDialogController { } catch (e) { return Promise.reject(e); } - this._queryRunner = this._instantiationService.createInstance(QueryRunner, this._connectionUri, undefined); + this._queryRunner = this._instantiationService.createInstance(QueryRunner, this._connectionUri); this.addQueryEventListeners(this._queryRunner); } diff --git a/src/sql/parts/profiler/editor/profilerEditor.ts b/src/sql/parts/profiler/editor/profilerEditor.ts index a3a09c0f7d..92de5a4bcb 100644 --- a/src/sql/parts/profiler/editor/profilerEditor.ts +++ b/src/sql/parts/profiler/editor/profilerEditor.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { ProfilerInput } from './profilerInput'; - import { TabbedPanel } from 'sql/base/browser/ui/panel/panel'; import { Table } from 'sql/base/browser/ui/table/table'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; @@ -17,15 +16,8 @@ import * as Actions from 'sql/parts/profiler/contrib/profilerActions'; import { CONTEXT_PROFILER_EDITOR, PROFILER_TABLE_COMMAND_SEARCH } from './interfaces'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; import { textFormatter } from 'sql/parts/grid/services/sharedServices'; -import * as DOM from 'vs/base/browser/dom'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { EditorOptions } from 'vs/workbench/common/editor'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkbenchThemeService, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ProfilerResourceEditor } from './profilerResourceEditor'; -import { SplitView, View, Orientation, IViewOptions } from 'sql/base/browser/ui/splitview/splitview'; + import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ITextModel } from 'vs/editor/common/model'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -39,59 +31,77 @@ import { Command } from 'vs/editor/browser/editorExtensions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ContextKeyExpr, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommonFindController, FindStartFocusAction } from 'vs/editor/contrib/find/findController'; import * as types from 'vs/base/common/types'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IView, SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import * as DOM from 'vs/base/browser/dom'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkbenchThemeService, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Event, Emitter } from 'vs/base/common/event'; +import { clamp } from 'vs/base/common/numbers'; -class BasicView extends View { - private _previousSize: number; - private _collapsed: boolean; - public headerSize: number; +class BasicView implements IView { + public get element(): HTMLElement { + return this._element; + } + private _onDidChange = new Emitter(); + public readonly onDidChange: Event = this._onDidChange.event; + + private _collapsed = false; + private size: number; + private previousSize: number; + private _minimumSize: number; + public get minimumSize(): number { + return this._minimumSize; + } + + private _maximumSize: number; + public get maximumSize(): number { + return this._maximumSize; + } constructor( - initialSize: number, + private _defaultMinimumSize: number, + private _defaultMaximumSize: number, + private _layout: (size: number) => void, private _element: HTMLElement, - private _focus: () => void, - private _layout: (size: number, orientation: Orientation) => void, - opts: IViewOptions + private options: { headersize?: number } = {} ) { - super(initialSize, opts); - this._previousSize = initialSize; + this._minimumSize = _defaultMinimumSize; + this._maximumSize = _defaultMaximumSize; } - render(container: HTMLElement, orientation: Orientation): void { - container.appendChild(this._element); + public layout(size: number): void { + this.size = size; + this._layout(size); } - focus(): void { - this._focus(); - } - - layout(size: number, orientation: Orientation): void { - if (!this.collapsed) { - this._previousSize = size; - } - this._layout(size, orientation); - } - - set collapsed(val: boolean) { - this._collapsed = val === false ? false : true; - if (this.collapsed) { - this._previousSize = this.size; - this.setFixed(this.headerSize); - } else { - // Enforce the min height for the view when user is doing expand operation, - // to make sure the view has a reasonable height. - const minHeight = 200; - this.setFlexible(Math.max(this._previousSize, minHeight)); + public set collapsed(val: boolean) { + if (val !== this._collapsed && this.options.headersize) { + this._collapsed = val; + if (this.collapsed) { + this.previousSize = this.size; + this._minimumSize = this.options.headersize; + this._maximumSize = this.options.headersize; + this._onDidChange.fire(); + } else { + this._maximumSize = this._defaultMaximumSize; + this._minimumSize = this._defaultMinimumSize; + this._onDidChange.fire(clamp(this.previousSize, this.minimumSize, this.maximumSize)); + } } } - get collapsed(): boolean { + public get collapsed(): boolean { return this._collapsed; } } @@ -146,14 +156,13 @@ export class ProfilerEditor extends BaseEditor { @IProfilerService private _profilerService: IProfilerService, @IContextKeyService private _contextKeyService: IContextKeyService, @IContextViewService private _contextViewService: IContextViewService, - @IEditorGroupsService private _editorGroupService: IEditorGroupsService, - @IEditorService private _editorService: IEditorService + @IEditorService editorService: IEditorService ) { super(ProfilerEditor.ID, telemetryService, themeService); this._profilerEditorContextKey = CONTEXT_PROFILER_EDITOR.bindTo(this._contextKeyService); - if (_editorService) { - _editorService.overrideOpenEditor((editor, options, group) => { + if (editorService) { + editorService.overrideOpenEditor((editor, options, group) => { if (this.isVisible() && (editor !== this.input || group !== this.group)) { this.saveEditorViewState(); } @@ -178,21 +187,19 @@ export class ProfilerEditor extends BaseEditor { let paneContainer = this._createProfilerPane(); this._splitView.addView(new BasicView( 300, - tableContainer, - () => this._profilerTableEditor.focus(), + Number.POSITIVE_INFINITY, size => this._profilerTableEditor.layout(new DOM.Dimension(parseFloat(DOM.getComputedStyle(this._body).width), size)), - {} - )); + tableContainer + ), Sizing.Distribute); this._panelView = new BasicView( 300, - paneContainer, - () => this._tabbedPanel.focus(), + Number.POSITIVE_INFINITY, size => this._tabbedPanel.layout(new DOM.Dimension(DOM.getTotalWidth(this._body), size)), - { minimumSize: 35 } + paneContainer, + { headersize: 35 } ); - this._panelView.headerSize = 35; - this._splitView.addView(this._panelView); + this._splitView.addView(this._panelView, Sizing.Distribute); } private _createHeader(): void { @@ -426,7 +433,6 @@ export class ProfilerEditor extends BaseEditor { isPanelCollapsed: true }); this._profilerTableEditor.updateState(); - this._splitView.layout(); this._profilerTableEditor.focus(); if (savedViewState) { this._profilerTableEditor.restoreViewState(savedViewState); diff --git a/src/sql/parts/profiler/editor/profilerState.ts b/src/sql/parts/profiler/editor/profilerState.ts index 4380c656d3..336f32451a 100644 --- a/src/sql/parts/profiler/editor/profilerState.ts +++ b/src/sql/parts/profiler/editor/profilerState.ts @@ -33,7 +33,7 @@ export class ProfilerState implements IDisposable { private _isPaused: boolean; private _isStopped: boolean; private _autoscroll: boolean; - private _isPanelCollapsed: boolean; + private _isPanelCollapsed = true; private _eventEmitter: EventEmitter; public get isConnected(): boolean { return this._isConnected; } diff --git a/src/sql/parts/query/editor/gridPanel.ts b/src/sql/parts/query/editor/gridPanel.ts index 9b365f955c..5ca2fbbc77 100644 --- a/src/sql/parts/query/editor/gridPanel.ts +++ b/src/sql/parts/query/editor/gridPanel.ts @@ -10,7 +10,7 @@ import { attachTableStyler } from 'sql/common/theme/styler'; import QueryRunner from 'sql/parts/query/execution/queryRunner'; import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView'; import { Table } from 'sql/base/browser/ui/table/table'; -import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview'; +import { ScrollableSplitView, IView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview'; import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin'; import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; import { SaveFormat } from 'sql/parts/grid/common/interfaces'; @@ -34,7 +34,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { range } from 'vs/base/common/arrays'; -import { Orientation, IView } from 'vs/base/browser/ui/splitview/splitview'; +import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { $ } from 'vs/base/browser/builder'; import { generateUuid } from 'vs/base/common/uuid'; @@ -476,10 +476,6 @@ class GridTable extends Disposable implements IView { this.scrolled = false; } - public render(container: HTMLElement, orientation: Orientation): void { - container.appendChild(this.container); - } - private build(): void { let tableContainer = document.createElement('div'); tableContainer.style.display = 'inline-block'; diff --git a/src/sql/platform/views/fixedCollapsibleView.ts b/src/sql/platform/views/fixedCollapsibleView.ts deleted file mode 100644 index 6ececec470..0000000000 --- a/src/sql/platform/views/fixedCollapsibleView.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import types = require('vs/base/common/types'); -import objects = require('vs/base/common/objects'); - -import { - ICollapsibleViewOptions, AbstractCollapsibleView, ViewSizing, CollapsibleState -} from 'sql/base/browser/ui/splitview/splitview'; - -export interface IFixedCollapsibleViewOptions extends ICollapsibleViewOptions { - expandedBodySize?: number; -} - -export abstract class FixedCollapsibleView extends AbstractCollapsibleView { - private _expandedBodySize: number; - - constructor(initialSize: number, opts: IFixedCollapsibleViewOptions) { - super(initialSize, objects.mixin({ sizing: ViewSizing.Fixed }, opts)); - this._expandedBodySize = types.isUndefined(opts.expandedBodySize) ? 22 : opts.expandedBodySize; - } - - get fixedSize(): number { return this.state === CollapsibleState.EXPANDED ? this.expandedSize : this.headerSize; } - private get expandedSize(): number { return this.expandedBodySize + this.headerSize; } - - get expandedBodySize(): number { return this._expandedBodySize; } - set expandedBodySize(size: number) { - this._expandedBodySize = size; - this.setFixed(this.fixedSize); - } - - protected changeState(state: CollapsibleState): void { - super.changeState(state); - this.setFixed(this.fixedSize); - - if (this.body) { - if (state === CollapsibleState.COLLAPSED) { - // make sure the body goes out of the tabindex world by hiding it - $(this.body).hide(); - } else { - $(this.body).show(); - } - } - } -} diff --git a/src/sql/platform/views/fixedListView.ts b/src/sql/platform/views/fixedListView.ts deleted file mode 100644 index 20c019c1dc..0000000000 --- a/src/sql/platform/views/fixedListView.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { CollapsibleView, ICollapsibleViewOptions } from 'sql/base/browser/ui/views/browser/views'; -import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IAction, ActionRunner } from 'vs/base/common/actions'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { $ } from 'vs/base/browser/builder'; -import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; -import { CollapsibleState } from 'sql/base/browser/ui/splitview/splitview'; - -export class FixedListView extends CollapsibleView { - private _badge: CountBadge; - private _disposables: IDisposable[] = []; - - constructor( - initialSize: number, - initiallyCollapsed: boolean, - private _viewTitle: string, - private _list: List, - private _bodyContainer: HTMLElement, - headerSize: number, - private _actions: IAction[], - actionRunner: ActionRunner, - contextMenuService: IContextMenuService, - keybindingService: IKeybindingService, - private _themeService: IThemeService - ) { - super(initialSize, { - id: _viewTitle, - name: _viewTitle, - actionRunner: actionRunner, - collapsed: initiallyCollapsed, - ariaHeaderLabel: _viewTitle, - sizing: headerSize, - initialBodySize: undefined - }, keybindingService, contextMenuService); - } - - // RENDER METHODS ////////////////////////////////////////////////////// - public renderBody(container: HTMLElement): void { - container.appendChild(this._bodyContainer); - } - - public renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - $('span').text(this._viewTitle).appendTo(titleDiv); - super.renderHeader(container); - - // show the badge - this._badge = new CountBadge($('.count-badge-wrapper').appendTo(container).getHTMLElement()); - this._disposables.push(attachBadgeStyler(this._badge, this._themeService)); - } - - public updateList(content: T[]) { - this._list.splice(0, this._list.length, content); - this._badge.setCount(this._list.length); - this._list.layout(this._list.contentHeight); - this.setFixed(this.fixedSize); - } - - public get list(): List { - return this._list; - } - - public listContentHeight(): number { - return this._list.contentHeight; - } - - public get fixedSize(): number { - return this.state === CollapsibleState.EXPANDED ? this.expandedSize : this.headerSize; - } - - private get expandedSize(): number { - if (this._list && this._list.contentHeight) { - return this._list.contentHeight + this.headerSize; - } - - return this.headerSize; - } - - protected changeState(state: CollapsibleState): void { - super.changeState(state); - this.setFixed(this.fixedSize); - if (this.list) { - this.list.getHTMLElement().hidden = (state === CollapsibleState.COLLAPSED); - } - } - - /** - * Return actions for the view - */ - public getActions(): IAction[] { - return this._actions; - } - - public dispose(): void { - this._disposables = dispose(this._disposables); - super.dispose(); - } -} \ No newline at end of file diff --git a/src/sqltest/parts/accountManagement/accountDialogController.test.ts b/src/sqltest/parts/accountManagement/accountDialogController.test.ts index bc249228e4..9571eb2d66 100644 --- a/src/sqltest/parts/accountManagement/accountDialogController.test.ts +++ b/src/sqltest/parts/accountManagement/accountDialogController.test.ts @@ -88,7 +88,7 @@ function createInstantiationService(addAccountFailureEmitter?: Emitter): .returns(() => undefined); // Create a mock account dialog - let accountDialog = new AccountDialog(null, null, null, instantiationService.object, null, null, null, new ContextKeyServiceStub(), null); + let accountDialog = new AccountDialog(null, null, instantiationService.object, null, null, null, null, new ContextKeyServiceStub(), null); let mockAccountDialog = TypeMoq.Mock.ofInstance(accountDialog); mockAccountDialog.setup(x => x.onAddAccountErrorEvent) .returns(() => { return addAccountFailureEmitter ? addAccountFailureEmitter.event : mockEvent.event; }); diff --git a/src/sqltest/parts/connection/advancedPropertiesDialog.test.ts b/src/sqltest/parts/connection/advancedPropertiesDialog.test.ts index 7bc9696730..c048936078 100644 --- a/src/sqltest/parts/connection/advancedPropertiesDialog.test.ts +++ b/src/sqltest/parts/connection/advancedPropertiesDialog.test.ts @@ -94,6 +94,7 @@ suite('Advanced properties dialog tests', () => { undefined, // partsService undefined, // themeService undefined, // Context view service + undefined, // instantiation Service undefined, // telemetry service new ContextKeyServiceStub() // contextkeyservice ); diff --git a/src/sqltest/parts/insights/insightsDialogController.test.ts b/src/sqltest/parts/insights/insightsDialogController.test.ts index ca787b83e1..3e36c56a76 100644 --- a/src/sqltest/parts/insights/insightsDialogController.test.ts +++ b/src/sqltest/parts/insights/insightsDialogController.test.ts @@ -35,7 +35,7 @@ suite('Insights Dialog Controller Tests', () => { let { runner, complete } = getPrimedQueryRunner(testData, testColumns); let instMoq = Mock.ofType(InstantiationService, MockBehavior.Strict); - instMoq.setup(x => x.createInstance(It.isValue(QueryRunner), It.isAny(), undefined)) + instMoq.setup(x => x.createInstance(It.isValue(QueryRunner), It.isAny())) .returns(() => runner); let connMoq = Mock.ofType(ConnectionManagementService, MockBehavior.Strict, {}, {}); diff --git a/src/sqltest/services/accountManagement/accountManagementService.test.ts b/src/sqltest/services/accountManagement/accountManagementService.test.ts index 8545547b0b..a0e259f632 100644 --- a/src/sqltest/services/accountManagement/accountManagementService.test.ts +++ b/src/sqltest/services/accountManagement/accountManagementService.test.ts @@ -109,7 +109,6 @@ suite('Account Management Service Tests:', () => { provider: mockProvider.object, metadata: hasAccountProvider }; - // If: I update an account that exists state.accountManagementService.accountUpdated(account) .then(() => { diff --git a/src/sqltest/workbench/api/extHostAccountManagement.test.ts b/src/sqltest/workbench/api/extHostAccountManagement.test.ts index 9dfd44532c..e8c2ee6ce5 100644 --- a/src/sqltest/workbench/api/extHostAccountManagement.test.ts +++ b/src/sqltest/workbench/api/extHostAccountManagement.test.ts @@ -296,7 +296,7 @@ suite('ExtHostAccountManagement', () => { let mockAccountManagementService = getMockAccountManagementService(mockAccounts); instantiationService.stub(IAccountManagementService, mockAccountManagementService.object); - let accountManagementService = instantiationService.createInstance(MainThreadAccountManagement); + let accountManagementService = instantiationService.createInstance(MainThreadAccountManagement, undefined); threadService.set(SqlMainContext.MainThreadAccountManagement, accountManagementService); // Setup: Create ext host account management with registered account provider