mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Add styling to selected tabs in BDC Dashboard (#6978)
* Change cursor over tabs to be hand (pointer) * Change hyperlink color * Hook up CSSStyles to be updated from extension side and make BDC dashboard tabs change style when selected * Remove unused file * Add back in call to updateStyles * Fix typos
This commit is contained in:
@@ -12,12 +12,17 @@ import { BdcDashboardModel } from './bdcDashboardModel';
|
||||
import { IconPathHelper } from '../constants';
|
||||
import { BdcServiceStatusPage } from './bdcServiceStatusPage';
|
||||
import { BdcDashboardOverviewPage } from './bdcDashboardOverviewPage';
|
||||
import { EndpointModel, BdcStatusModel, ServiceStatusModel } from '../controller/apiGenerated';
|
||||
import { BdcStatusModel, ServiceStatusModel } from '../controller/apiGenerated';
|
||||
import { getHealthStatusDot, getServiceNameDisplayText } from '../utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const navWidth = '175px';
|
||||
const navWidth = '200px';
|
||||
|
||||
const selectedTabCss = { 'font-weight': 'bold' };
|
||||
const unselectedTabCss = { 'font-weight': '' };
|
||||
|
||||
type NavTab = { div: azdata.DivContainer, dot: azdata.TextComponent, text: azdata.TextComponent };
|
||||
|
||||
export class BdcDashboard {
|
||||
|
||||
@@ -29,6 +34,8 @@ export class BdcDashboard {
|
||||
private modelView: azdata.ModelView;
|
||||
private mainAreaContainer: azdata.FlexContainer;
|
||||
private navContainer: azdata.FlexContainer;
|
||||
|
||||
private currentTab: NavTab;
|
||||
private currentPage: azdata.FlexContainer;
|
||||
|
||||
constructor(private title: string, private model: BdcDashboardModel) {
|
||||
@@ -117,18 +124,26 @@ export class BdcDashboard {
|
||||
this.mainAreaContainer.addItem(this.navContainer, { flex: `0 0 ${navWidth}`, CSSStyles: { 'padding-left': '10px', 'border-right': 'solid 1px #ccc' } });
|
||||
|
||||
// Overview nav item - this will be the initial page
|
||||
const overviewNavItem = modelView.modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px' }).component();
|
||||
overviewNavItem.addItem(modelView.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.overviewNavTitle', 'Big data cluster overview') }).component(), { CSSStyles: { 'user-select': 'text' } });
|
||||
const overviewNavItemDiv = modelView.modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px' }).withProperties({ CSSStyles: { 'cursor': 'pointer' } }).component();
|
||||
const overviewNavItemText = modelView.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.overviewNavTitle', 'Big data cluster overview') }).component();
|
||||
overviewNavItemText.updateCssStyles(selectedTabCss);
|
||||
overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } });
|
||||
const overviewPage = new BdcDashboardOverviewPage(this.model).create(modelView);
|
||||
this.currentPage = overviewPage;
|
||||
this.currentTab = { div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
||||
this.mainAreaContainer.addItem(overviewPage, { flex: '0 0 100%' });
|
||||
|
||||
overviewNavItem.onDidClick(() => {
|
||||
overviewNavItemDiv.onDidClick(() => {
|
||||
if (this.currentTab) {
|
||||
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
||||
}
|
||||
this.mainAreaContainer.removeItem(this.currentPage);
|
||||
this.mainAreaContainer.addItem(overviewPage, { flex: '0 0 100%' });
|
||||
this.currentPage = overviewPage;
|
||||
this.currentTab = { div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
||||
this.currentTab.text.updateCssStyles(selectedTabCss);
|
||||
});
|
||||
this.navContainer.addItem(overviewNavItem, { flex: '0 0 auto' });
|
||||
this.navContainer.addItem(overviewNavItemDiv, { flex: '0 0 auto' });
|
||||
|
||||
const clusterDetailsHeader = modelView.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.clusterDetails', 'Cluster Details'), CSSStyles: { 'margin-block-end': '0px' } }).component();
|
||||
this.navContainer.addItem(clusterDetailsHeader, { CSSStyles: { 'user-select': 'none', 'font-weight': 'bold', 'border-bottom': 'solid 1px #ccc', 'margin-bottom': '10px' } });
|
||||
@@ -159,23 +174,30 @@ export class BdcDashboard {
|
||||
services.forEach(s => {
|
||||
const navItem = createServiceNavTab(this.modelView.modelBuilder, s);
|
||||
const serviceStatusPage = new BdcServiceStatusPage(s.serviceName, this.model, this.modelView).container;
|
||||
navItem.onDidClick(() => {
|
||||
navItem.div.onDidClick(() => {
|
||||
if (this.currentTab) {
|
||||
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
||||
}
|
||||
this.mainAreaContainer.removeItem(this.currentPage);
|
||||
this.mainAreaContainer.addItem(serviceStatusPage);
|
||||
this.currentPage = serviceStatusPage;
|
||||
this.currentTab = navItem;
|
||||
this.currentTab.text.updateCssStyles(selectedTabCss);
|
||||
});
|
||||
this.navContainer.addItem(navItem, { flex: '0 0 auto' });
|
||||
this.navContainer.addItem(navItem.div, { flex: '0 0 auto' });
|
||||
});
|
||||
this.serviceTabsCreated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createServiceNavTab(modelBuilder: azdata.ModelBuilder, serviceStatus: ServiceStatusModel): azdata.DivContainer {
|
||||
const div = modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px' }).component();
|
||||
function createServiceNavTab(modelBuilder: azdata.ModelBuilder, serviceStatus: ServiceStatusModel): NavTab {
|
||||
const div = modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px' }).withProperties({ CSSStyles: { 'cursor': 'pointer' } }).component();
|
||||
const innerContainer = modelBuilder.flexContainer().withLayout({ width: navWidth, height: '30px', flexFlow: 'row' }).component();
|
||||
innerContainer.addItem(modelBuilder.text().withProperties({ value: getHealthStatusDot(serviceStatus.healthStatus), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'none', 'color': 'red', 'font-size': '40px', 'width': '20px' } }).component(), { flex: '0 0 auto' });
|
||||
innerContainer.addItem(modelBuilder.text().withProperties({ value: getServiceNameDisplayText(serviceStatus.serviceName), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component(), { flex: '0 0 auto' });
|
||||
const dot = modelBuilder.text().withProperties({ value: getHealthStatusDot(serviceStatus.healthStatus), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'none', 'color': 'red', 'font-size': '40px', 'width': '20px' } }).component();
|
||||
innerContainer.addItem(dot, { flex: '0 0 auto' });
|
||||
const text = modelBuilder.text().withProperties({ value: getServiceNameDisplayText(serviceStatus.serviceName), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'none' } }).component();
|
||||
innerContainer.addItem(text, { flex: '0 0 auto' });
|
||||
div.addItem(innerContainer);
|
||||
return div;
|
||||
return { div: div, dot: dot, text: text };
|
||||
}
|
||||
|
||||
@@ -200,9 +200,9 @@ function createMetricsAndLogsRow(modelBuilder: azdata.ModelBuilder, instanceStat
|
||||
const metricsAndLogsRow = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
|
||||
const nameCell = modelBuilder.text().withProperties({ value: instanceStatus.instanceName, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' } }).component();
|
||||
metricsAndLogsRow.addItem(nameCell, { CSSStyles: { 'width': '100px', 'min-width': '100px', 'user-select': 'text', 'margin-block-start': '0px', 'margin-block-end': '0px' } });
|
||||
const metricsCell = modelBuilder.hyperlink().withProperties({ label: localize('bdc.dashboard.viewHyperlink', "View"), url: instanceStatus.dashboards.nodeMetricsUrl, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
|
||||
const metricsCell = modelBuilder.hyperlink().withProperties({ label: localize('bdc.dashboard.viewHyperlink', "View"), url: instanceStatus.dashboards.nodeMetricsUrl, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline' } }).component();
|
||||
metricsAndLogsRow.addItem(metricsCell, { CSSStyles: { 'width': '75px', 'min-width': '75px' } });
|
||||
const logsCell = modelBuilder.hyperlink().withProperties({ label: localize('bdc.dashboard.viewHyperlink', "View"), url: instanceStatus.dashboards.logsUrl, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
|
||||
const logsCell = modelBuilder.hyperlink().withProperties({ label: localize('bdc.dashboard.viewHyperlink', "View"), url: instanceStatus.dashboards.logsUrl, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline' } }).component();
|
||||
metricsAndLogsRow.addItem(logsCell, { CSSStyles: { 'width': '75px', 'min-width': '75px' } });
|
||||
|
||||
return metricsAndLogsRow;
|
||||
|
||||
@@ -10,11 +10,15 @@ import { BdcDashboardResourceStatusPage } from './bdcDashboardResourceStatusPage
|
||||
import { BdcDashboardModel } from './bdcDashboardModel';
|
||||
import { getHealthStatusDot } from '../utils';
|
||||
|
||||
const selectedTabCss = { 'font-weight': 'bold' };
|
||||
const unselectedTabCss = { 'font-weight': '' };
|
||||
|
||||
export class BdcServiceStatusPage {
|
||||
|
||||
private initialized: boolean = false;
|
||||
private resourceTabsCreated: boolean = false;
|
||||
|
||||
private currentTabText: azdata.TextComponent;
|
||||
private currentTabPage: azdata.FlexContainer;
|
||||
private rootContainer: azdata.FlexContainer;
|
||||
private resourceHeader: azdata.FlexContainer;
|
||||
@@ -78,13 +82,20 @@ export class BdcServiceStatusPage {
|
||||
resources.forEach(resource => {
|
||||
const resourceHeaderTab = createResourceHeaderTab(this.modelView.modelBuilder, resource);
|
||||
const resourceStatusPage: azdata.FlexContainer = new BdcDashboardResourceStatusPage(this.model, this.modelView, this.serviceName, resource.resourceName).container;
|
||||
resourceHeaderTab.onDidClick(() => {
|
||||
resourceHeaderTab.div.onDidClick(() => {
|
||||
if (this.currentTabText) {
|
||||
this.currentTabText.updateCssStyles(unselectedTabCss);
|
||||
}
|
||||
this.changeSelectedTabPage(resourceStatusPage);
|
||||
this.currentTabText = resourceHeaderTab.text;
|
||||
this.currentTabText.updateCssStyles(selectedTabCss);
|
||||
});
|
||||
if (!this.currentTabPage) {
|
||||
this.changeSelectedTabPage(resourceStatusPage);
|
||||
this.currentTabText = resourceHeaderTab.text;
|
||||
this.currentTabText.updateCssStyles(selectedTabCss);
|
||||
}
|
||||
this.resourceHeader.addItem(resourceHeaderTab, { flex: '0 0 auto', CSSStyles: { 'border-bottom': 'solid #ccc' } });
|
||||
this.resourceHeader.addItem(resourceHeaderTab.div, { flex: '0 0 auto', CSSStyles: { 'border-bottom': 'solid #ccc' } });
|
||||
});
|
||||
this.resourceTabsCreated = true;
|
||||
}
|
||||
@@ -96,12 +107,12 @@ export class BdcServiceStatusPage {
|
||||
* @param modelBuilder The ModelBuilder used to construct the object
|
||||
* @param title The text to display in the tab
|
||||
*/
|
||||
function createResourceHeaderTab(modelBuilder: azdata.ModelBuilder, resourceStatus: ResourceStatusModel): azdata.DivContainer {
|
||||
const resourceHeaderTab = modelBuilder.divContainer().withLayout({ width: '100px', height: '25px' }).component();
|
||||
function createResourceHeaderTab(modelBuilder: azdata.ModelBuilder, resourceStatus: ResourceStatusModel): { div: azdata.DivContainer, text: azdata.TextComponent } {
|
||||
const resourceHeaderTab = modelBuilder.divContainer().withLayout({ width: '100px', height: '25px' }).withProperties({ CSSStyles: { 'cursor': 'pointer' } }).component();
|
||||
const innerContainer = modelBuilder.flexContainer().withLayout({ width: '100px', height: '25px', flexFlow: 'row' }).component();
|
||||
innerContainer.addItem(modelBuilder.text().withProperties({ value: getHealthStatusDot(resourceStatus.healthStatus), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'none', 'color': 'red', 'font-size': '40px', 'width': '20px', 'text-align': 'right' } }).component(), { flex: '0 0 auto' });
|
||||
const resourceHeaderLabel = modelBuilder.text().withProperties({ value: resourceStatus.resourceName, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'text-align': 'left' } }).component();
|
||||
innerContainer.addItem(resourceHeaderLabel);
|
||||
resourceHeaderTab.addItem(innerContainer);
|
||||
return resourceHeaderTab;
|
||||
return { div: resourceHeaderTab, text: resourceHeaderLabel };
|
||||
}
|
||||
|
||||
7
src/sql/azdata.d.ts
vendored
7
src/sql/azdata.d.ts
vendored
@@ -2543,6 +2543,13 @@ declare module 'azdata' {
|
||||
*/
|
||||
updateProperty(key: string, value: any): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Updates the specified CSS Styles and notifies the UI
|
||||
* @param cssStyles The styles to update
|
||||
* @returns Thenable that completes once the update has been applied to the UI
|
||||
*/
|
||||
updateCssStyles(cssStyles: { [key: string]: string }): Thenable<void>;
|
||||
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Event fired to notify that the component's validity has changed
|
||||
|
||||
7
src/sql/sqlops.proposed.d.ts
vendored
7
src/sql/sqlops.proposed.d.ts
vendored
@@ -161,6 +161,13 @@ declare module 'sqlops' {
|
||||
*/
|
||||
updateProperty(key: string, value: any): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Updates the specified CSS Styles and notifies the UI
|
||||
* @param cssStyles The styles to update
|
||||
* @returns Thenable that completes once the update has been applied to the UI
|
||||
*/
|
||||
updateCssStyles(cssStyles: { [key: string]: string }): Thenable<void>;
|
||||
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Event fired to notify that the component's validity has changed
|
||||
|
||||
@@ -532,6 +532,14 @@ class ComponentWrapper implements azdata.Component {
|
||||
this.setProperty('required', v);
|
||||
}
|
||||
|
||||
public get CSSStyles(): { [key: string]: string } {
|
||||
return this.properties['CSSStyles'];
|
||||
}
|
||||
|
||||
public set CSSStyles(cssStyles: { [key: string]: string }) {
|
||||
this.setProperty('CSSStyles', cssStyles);
|
||||
}
|
||||
|
||||
public toComponentShape(): IComponentShape {
|
||||
return <IComponentShape>{
|
||||
id: this.id,
|
||||
@@ -604,6 +612,11 @@ class ComponentWrapper implements azdata.Component {
|
||||
return this.setProperty(key, value);
|
||||
}
|
||||
|
||||
public updateCssStyles(cssStyles: { [key: string]: string }): Thenable<void> {
|
||||
this.properties.CSSStyles = Object.assign(this.properties.CSSStyles || {}, cssStyles);
|
||||
return this.notifyPropertyChanged();
|
||||
}
|
||||
|
||||
protected notifyPropertyChanged(): Thenable<void> {
|
||||
return this._proxy.$setProperties(this._handle, this._id, this.properties);
|
||||
}
|
||||
@@ -626,7 +639,6 @@ class ComponentWrapper implements azdata.Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected setDataProvider(): Thenable<void> {
|
||||
return this._proxy.$setDataProvider(this._handle, this._id);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
private _valid: boolean = true;
|
||||
protected _validations: (() => boolean | Thenable<boolean>)[] = [];
|
||||
private _eventQueue: IComponentEventArgs[] = [];
|
||||
private _CSSStyles: { [key: string]: string } = {};
|
||||
|
||||
constructor(
|
||||
protected _changeRef: ChangeDetectorRef,
|
||||
@@ -82,7 +81,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
public refreshDataProvider(item: any): void {
|
||||
}
|
||||
|
||||
public updateStyles() {
|
||||
public updateStyles(): void {
|
||||
const element = (<HTMLElement>this._el.nativeElement);
|
||||
for (const style in this.CSSStyles) {
|
||||
element.style[style] = this.CSSStyles[style];
|
||||
@@ -90,13 +89,9 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
}
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
if (!properties) {
|
||||
this.properties = {};
|
||||
}
|
||||
properties = properties || {};
|
||||
this.properties = properties;
|
||||
if (this.CSSStyles !== this._CSSStyles) {
|
||||
this.updateStyles();
|
||||
}
|
||||
this.updateStyles();
|
||||
this.layout();
|
||||
this.validate();
|
||||
}
|
||||
@@ -105,10 +100,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
public updateProperty(key: string, value: any): void {
|
||||
if (key) {
|
||||
this.properties[key] = value;
|
||||
|
||||
if (this.CSSStyles !== this._CSSStyles) {
|
||||
this.updateStyles();
|
||||
}
|
||||
this.updateStyles();
|
||||
this.layout();
|
||||
this.validate();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user