mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 01:25:36 -05:00
Change angular panel display behavior (#1344)
* got it working * remove unneeded code * formatting * added scrollable, dashboard tabs don't scroll correctly though * fix all bugs I could find * address comments
This commit is contained in:
@@ -21,6 +21,12 @@ panel {
|
||||
.tabbedPanel .composite.title {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabContainer {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabList {
|
||||
@@ -31,6 +37,7 @@ panel {
|
||||
line-height: 35px;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabList .tab {
|
||||
@@ -88,17 +95,13 @@ panel {
|
||||
}
|
||||
|
||||
.composite.title .title-actions {
|
||||
flex: 1 1 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tab > .tabLabel.active {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.composite.title ~ tab.fullsize > :first-child {
|
||||
height: calc(100% - 38px);
|
||||
}
|
||||
|
||||
.tabbedPanel .title-actions .panel-actions .actions-container {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import { Component, ContentChildren, QueryList, AfterContentInit, Inject, forwar
|
||||
|
||||
import { TabComponent } from './tab.component';
|
||||
import { TabHeaderComponent } from './tabHeader.component';
|
||||
import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive';
|
||||
import './panelStyles';
|
||||
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -46,12 +46,13 @@ let idPool = 0;
|
||||
@Component({
|
||||
selector: 'panel',
|
||||
template: `
|
||||
|
||||
<div class="tabbedPanel fullsize" #tabbedPanel>
|
||||
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title" #titleContainer>
|
||||
<div class="tabList" #tabList role="tablist">
|
||||
<div *ngFor="let tab of _tabs">
|
||||
<tab-header [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'> </tab-header>
|
||||
<div class="tabbedPanel fullsize" [ngClass]="options.layout === NavigationBarLayout.vertical ? 'vertical' : 'horizontal'">
|
||||
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title">
|
||||
<div class="tabContainer">
|
||||
<div class="tabList" role="tablist" scrollable [horizontalScroll]="ScrollbarVisibility.Auto" [verticalScroll]="ScrollbarVisibility.Hidden" [scrollYToX]="true">
|
||||
<div *ngFor="let tab of _tabs">
|
||||
<tab-header [active]="_activeTab === tab" [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'> </tab-header>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title-actions">
|
||||
@@ -67,11 +68,12 @@ let idPool = 0;
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class PanelComponent extends Disposable implements AfterContentInit, OnInit, OnChanges, OnDestroy, AfterViewInit {
|
||||
export class PanelComponent extends Disposable {
|
||||
@Input() public options: IPanelOptions;
|
||||
@Input() public actions: Array<Action>;
|
||||
@ContentChildren(TabComponent) private _tabs: QueryList<TabComponent>;
|
||||
@ViewChildren(TabHeaderComponent) private _headerTabs: QueryList<TabHeaderComponent>;
|
||||
@ViewChild(ScrollableDirective) private scrollable: ScrollableDirective;
|
||||
|
||||
@Output() public onTabChange = new EventEmitter<TabComponent>();
|
||||
@Output() public onTabClose = new EventEmitter<TabComponent>();
|
||||
@@ -79,12 +81,11 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
private _activeTab: TabComponent;
|
||||
private _actionbar: ActionBar;
|
||||
private _mru: TabComponent[];
|
||||
private _scrollableElement: ScrollableElement;
|
||||
|
||||
private ScrollbarVisibility = ScrollbarVisibility;
|
||||
private NavigationBarLayout = NavigationBarLayout;
|
||||
|
||||
@ViewChild('panelActionbar', { read: ElementRef }) private _actionbarRef: ElementRef;
|
||||
@ViewChild('tabbedPanel', { read: ElementRef }) private _tabbedPanelRef: ElementRef;
|
||||
@ViewChild('titleContainer', { read: ElementRef }) private _titleContainer: ElementRef;
|
||||
@ViewChild('tabList', { read: ElementRef }) private _tabList: ElementRef;
|
||||
constructor( @Inject(forwardRef(() => NgZone)) private _zone: NgZone) {
|
||||
super();
|
||||
}
|
||||
@@ -96,51 +97,14 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
if (this._tabs && this._tabs.length > 0) {
|
||||
this._activeTab = this._tabs.first;
|
||||
this._activeTab.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (!this.options.showTabsWhenOne ? this._tabs.length !== 1 : true) {
|
||||
let container = this._titleContainer.nativeElement as HTMLElement;
|
||||
let tabList = this._tabList.nativeElement as HTMLElement;
|
||||
container.removeChild(tabList);
|
||||
|
||||
this._scrollableElement = new ScrollableElement(tabList, {
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: ScrollbarVisibility.Hidden,
|
||||
scrollYToX: true,
|
||||
useShadows: false,
|
||||
horizontalScrollbarSize: 3
|
||||
this.selectTab(this._tabs.first);
|
||||
} else {
|
||||
const sub = this._tabs.changes.subscribe(() => {
|
||||
if (this._tabs && this._tabs.length > 0) {
|
||||
this.selectTab(this._tabs.first);
|
||||
sub.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
this._scrollableElement.onScroll(e => {
|
||||
tabList.scrollLeft = e.scrollLeft;
|
||||
});
|
||||
|
||||
container.insertBefore(this._scrollableElement.getDomNode(), container.firstChild);
|
||||
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
width: tabList.offsetWidth,
|
||||
scrollWidth: tabList.scrollWidth
|
||||
});
|
||||
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
// Todo: Need to set timeout because we have to make sure that the grids have already rearraged before the getContentHeight gets called.
|
||||
setTimeout(() => {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
width: tabList.offsetWidth,
|
||||
scrollWidth: tabList.scrollWidth
|
||||
});
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
if (this.options.layout === NavigationBarLayout.horizontal) {
|
||||
(<HTMLElement>this._tabbedPanelRef.nativeElement).classList.add(horizontalLayout);
|
||||
} else {
|
||||
(<HTMLElement>this._tabbedPanelRef.nativeElement).classList.add(verticalLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +118,17 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._tabs.changes.subscribe(() => {
|
||||
if (this.scrollable) {
|
||||
this.scrollable.layout();
|
||||
}
|
||||
});
|
||||
if (this.scrollable) {
|
||||
this.scrollable.layout();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this._actionbar) {
|
||||
this._actionbar.dispose();
|
||||
@@ -210,12 +185,6 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
this.setMostRecentlyUsed(tab);
|
||||
this._activeTab.active = true;
|
||||
|
||||
// Make the tab header focus on the new selected tab
|
||||
let activeTabHeader = this._headerTabs.find(i => i.tab === this._activeTab);
|
||||
if (activeTabHeader) {
|
||||
activeTabHeader.focusOnTabHeader();
|
||||
}
|
||||
|
||||
this.onTabChange.emit(tab);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ import { TabComponent } from './tab.component';
|
||||
import { TabHeaderComponent } from './tabHeader.component';
|
||||
import { PanelComponent } from './panel.component';
|
||||
|
||||
import { ScrollableModule } from 'sql/base/browser/ui/scrollable/scrollable.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
imports: [CommonModule, ScrollableModule],
|
||||
exports: [TabComponent, PanelComponent],
|
||||
declarations: [TabComponent, TabHeaderComponent, PanelComponent]
|
||||
})
|
||||
export class PanelModule { }
|
||||
export class PanelModule { }
|
||||
|
||||
@@ -15,13 +15,13 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
|
||||
if (titleActive || titleActiveBorder) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab:hover .tabLabel,
|
||||
.tabbedPanel > .title > .tabList .tab .tabLabel.active {
|
||||
.tabbedPanel > .title .tabList .tab:hover .tabLabel,
|
||||
.tabbedPanel > .title .tabList .tab .tabLabel.active {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${titleActiveBorder};
|
||||
}
|
||||
|
||||
.tabbedPanel > .title > .tabList .tab-header.active {
|
||||
.tabbedPanel > .title .tabList .tab-header.active {
|
||||
outline: none;
|
||||
}
|
||||
`);
|
||||
@@ -31,7 +31,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
|
||||
if (titleInactive) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab .tabLabel {
|
||||
.tabbedPanel > .title .tabList .tab .tabLabel {
|
||||
color: ${titleInactive};
|
||||
}
|
||||
`);
|
||||
@@ -41,7 +41,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab .tabLabel:focus {
|
||||
.tabbedPanel > .title .tabList .tab .tabLabel:focus {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${focusBorderColor} !important;
|
||||
border-bottom: 1px solid;
|
||||
@@ -54,8 +54,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab-header.active,
|
||||
.tabbedPanel > .title > .tabList .tab-header:hover {
|
||||
.tabbedPanel > .title .tabList .tab-header.active,
|
||||
.tabbedPanel > .title .tabList .tab-header:hover {
|
||||
outline-color: ${outline};
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
@@ -63,7 +63,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
.tabbedPanel > .title > .tabList .tab-header:hover:not(.active) {
|
||||
.tabbedPanel > .title .tabList .tab-header:hover:not(.active) {
|
||||
outline-style: dashed;
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Component, Input, ContentChild, OnDestroy } from '@angular/core';
|
||||
import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
|
||||
@@ -13,13 +13,14 @@ export abstract class TabChild {
|
||||
@Component({
|
||||
selector: 'tab',
|
||||
template: `
|
||||
<div class="visibility" [class.hidden]="!active && visibilityType == 'visibility'" *ngIf="visibilityType == 'visibility' || active" class="fullsize">
|
||||
<ng-content class="body fullsize"></ng-content>
|
||||
<div class="visibility" [class.hidden]="shouldBeHidden()" *ngIf="shouldBeIfed()" class="fullsize">
|
||||
<ng-container *ngTemplateOutlet="templateRef"></ng-container>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class TabComponent implements OnDestroy {
|
||||
@ContentChild(TabChild) private _child: TabChild;
|
||||
@ContentChild(TemplateRef) templateRef;
|
||||
@Input() public title: string;
|
||||
@Input() public canClose: boolean;
|
||||
@Input() public actions: Array<Action>;
|
||||
@@ -27,9 +28,18 @@ export class TabComponent implements OnDestroy {
|
||||
public _active = false;
|
||||
@Input() public identifier: string;
|
||||
@Input() private visibilityType: 'if' | 'visibility' = 'if';
|
||||
private rendered = false;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
|
||||
) { }
|
||||
|
||||
public set active(val: boolean) {
|
||||
this._active = val;
|
||||
if (this.active) {
|
||||
this.rendered = true;
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
@@ -45,4 +55,21 @@ export class TabComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeIfed(): boolean {
|
||||
if (this.active) {
|
||||
return true;
|
||||
} else if (this.visibilityType === 'visibility' && this.rendered) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeHidden(): boolean {
|
||||
if (this.visibilityType === 'visibility' && !this.active) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { CloseTabAction } from './tabActions';
|
||||
@Component({
|
||||
selector: 'tab-header',
|
||||
template: `
|
||||
<div #actionHeader class="tab-header" style="flex: 0 0; flex-direction: row;" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
|
||||
<div #actionHeader class="tab-header" style="flex: 0 0; flex-direction: row; height: 100%" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
|
||||
<span class="tab" (click)="selectTab(tab)" role="tab" [attr.aria-selected]="tab.active" [attr.aria-label]="tab.title">
|
||||
<a class="tabLabel" [class.active]="tab.active" #tabLabel>
|
||||
</a>
|
||||
@@ -31,6 +31,7 @@ import { CloseTabAction } from './tabActions';
|
||||
export class TabHeaderComponent extends Disposable implements AfterContentInit, OnDestroy {
|
||||
@Input() public tab: TabComponent;
|
||||
@Input() public showIcon: boolean;
|
||||
@Input() public active: boolean;
|
||||
@Output() public onSelectTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
||||
@Output() public onCloseTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Directive, Inject, forwardRef, ElementRef } from '@angular/core';
|
||||
import { Directive, Inject, forwardRef, ElementRef, Input } from '@angular/core';
|
||||
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { getContentHeight, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { getContentHeight, addDisposableListener, EventType, getContentWidth } from 'vs/base/browser/dom';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
@Directive({
|
||||
@@ -17,33 +17,45 @@ export class ScrollableDirective extends AngularDisposable {
|
||||
private scrollableElement: ScrollableElement;
|
||||
private parent: HTMLElement;
|
||||
private scrolled: HTMLElement;
|
||||
@Input() horizontalScroll: ScrollbarVisibility;
|
||||
@Input() verticalScroll: ScrollbarVisibility;
|
||||
@Input() useShadow = false;
|
||||
@Input() scrollYToX = false;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.scrolled = this._el.nativeElement as HTMLElement;
|
||||
this.parent = this.scrolled.parentElement;
|
||||
const next = this.scrolled.nextSibling;
|
||||
this.parent.removeChild(this.scrolled);
|
||||
|
||||
this.scrolled.style.position = 'relative';
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.scrolled, {
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
useShadows: false
|
||||
horizontal: this.horizontalScroll,
|
||||
vertical: this.verticalScroll,
|
||||
useShadows: this.useShadow,
|
||||
scrollYToX: this.scrollYToX,
|
||||
horizontalScrollbarSize: 3
|
||||
});
|
||||
|
||||
this.scrollableElement.onScroll(e => {
|
||||
this.scrolled.style.bottom = e.scrollTop + 'px';
|
||||
if (this.verticalScroll === ScrollbarVisibility.Auto) {
|
||||
this.scrolled.style.bottom = e.scrollTop + 'px';
|
||||
} else if (this.horizontalScroll === ScrollbarVisibility.Auto) {
|
||||
this.scrolled.scrollLeft = e.scrollLeft;
|
||||
}
|
||||
});
|
||||
|
||||
this.parent.appendChild(this.scrollableElement.getDomNode());
|
||||
this.parent.insertBefore(this.scrollableElement.getDomNode(), next);
|
||||
const initialHeight = getContentHeight(this.scrolled);
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this.scrolled),
|
||||
height: getContentHeight(this.parent)
|
||||
});
|
||||
const initalWidth = getContentWidth(this.scrolled);
|
||||
this.resetScrollDimensions();
|
||||
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
this.resetScrollDimensions();
|
||||
@@ -52,24 +64,23 @@ export class ScrollableDirective extends AngularDisposable {
|
||||
// unforunately because of angular rendering behavior we need to do a double check to make sure nothing changed after this point
|
||||
setTimeout(() => {
|
||||
let currentheight = getContentHeight(this.scrolled);
|
||||
if (initialHeight !== currentheight) {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: currentheight,
|
||||
height: getContentHeight(this.parent)
|
||||
});
|
||||
let currentWidth = getContentWidth(this.scrolled);
|
||||
if (initialHeight !== currentheight || initalWidth !== currentWidth) {
|
||||
this.resetScrollDimensions();
|
||||
}
|
||||
}, 200);
|
||||
|
||||
}
|
||||
|
||||
private resetScrollDimensions() {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this.scrolled),
|
||||
height: getContentHeight(this.parent)
|
||||
scrollHeight: this.verticalScroll === ScrollbarVisibility.Auto ? getContentHeight(this.scrolled) : undefined,
|
||||
height: this.verticalScroll === ScrollbarVisibility.Auto ? getContentHeight(this.parent) : undefined,
|
||||
scrollWidth: this.horizontalScroll === ScrollbarVisibility.Auto ? this.scrolled.scrollWidth : undefined,
|
||||
width: this.horizontalScroll === ScrollbarVisibility.Auto ? this.scrolled.offsetWidth : undefined
|
||||
});
|
||||
}
|
||||
|
||||
public layout() {
|
||||
|
||||
this.resetScrollDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
15
src/sql/base/browser/ui/scrollable/scrollable.module.ts
Normal file
15
src/sql/base/browser/ui/scrollable/scrollable.module.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ScrollableDirective } from './scrollable.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [ScrollableDirective],
|
||||
declarations: [ScrollableDirective]
|
||||
})
|
||||
export class ScrollableModule { }
|
||||
Reference in New Issue
Block a user