From 8230d39120a80acf647489c5a23f9e64b735af4f Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Mon, 25 Oct 2021 11:25:19 -0700 Subject: [PATCH] add designer property grouping support (#17485) * add properties grouping support * revert unexpected changes * use common color --- src/sql/base/browser/ui/designer/designer.ts | 90 +++++++++++++++---- .../ui/designer/designerPropertiesPane.ts | 42 ++++----- .../ui/designer/designerTabPanelView.ts | 12 +-- .../platform/theme/common/colorRegistry.ts | 3 + src/sql/platform/theme/common/styler.ts | 3 +- .../workbench/browser/modal/optionsDialog.ts | 3 +- 6 files changed, 104 insertions(+), 49 deletions(-) diff --git a/src/sql/base/browser/ui/designer/designer.ts b/src/sql/base/browser/ui/designer/designer.ts index f0cf253ad9..dc0f7ae1e3 100644 --- a/src/sql/base/browser/ui/designer/designer.ts +++ b/src/sql/base/browser/ui/designer/designer.ts @@ -37,11 +37,12 @@ export interface IDesignerStyle { checkboxStyles?: ICheckboxStyles; buttonStyles?: IButtonStyles; paneSeparator?: Color; + groupHeaderBackground?: Color; } export type DesignerUIComponent = InputBox | Checkbox | Table | SelectBox; -export type CreateComponentFunc = (container: HTMLElement, component: DesignerDataPropertyInfo, editIdentifier: DesignerEditIdentifier) => DesignerUIComponent; +export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], editIdentifierGetter: (property: DesignerDataPropertyInfo) => DesignerEditIdentifier) => DesignerUIComponent[]; export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void; export class Designer extends Disposable implements IThemable { @@ -65,6 +66,7 @@ export class Designer extends Disposable implements IThemable { private _buttons: Button[] = []; private _inputDisposable: DisposableStore; private _loadingTimeoutHandle: any; + private _groupHeaders: HTMLElement[] = []; constructor(private readonly _container: HTMLElement, private readonly _contextViewProvider: IContextViewProvider) { @@ -143,12 +145,10 @@ export class Designer extends Disposable implements IThemable { onDidChange: Event.None }, Sizing.Distribute); - this._propertiesPane = new DesignerPropertiesPane(this._propertiesPaneContainer, (container, component, identifier) => { - return this.createComponent(container, component, identifier, false, false); + this._propertiesPane = new DesignerPropertiesPane(this._propertiesPaneContainer, (container, components, identifierGetter) => { + return this.createComponents(container, components, this._propertiesPane.componentMap, this._propertiesPane.groupHeaders, identifierGetter, false, true); }, (definition, component, viewModel) => { this.setComponentValue(definition, component, viewModel); - }, (component) => { - this.styleComponent(component); }); const editor = DOM.$('div'); editor.innerText = 'script pane placeholder'; @@ -171,6 +171,12 @@ export class Designer extends Disposable implements IThemable { } } + private styleGroupHeader(header: HTMLElement): void { + if (this._styles.groupHeaderBackground) { + header.style.backgroundColor = this._styles.groupHeaderBackground.toString(); + } + } + public style(styles: IDesignerStyle): void { this._styles = styles; this._componentMap.forEach((value, key, map) => { @@ -178,7 +184,9 @@ export class Designer extends Disposable implements IThemable { this.styleComponent(value.component); } }); - this._propertiesPane.style(); + this._propertiesPane.componentMap.forEach((value) => { + this.styleComponent(value.component); + }); this._verticalSplitView.style({ separatorBorder: styles.paneSeparator }); @@ -190,6 +198,14 @@ export class Designer extends Disposable implements IThemable { this._buttons.forEach((button) => { this.styleComponent(button); }); + + this._groupHeaders.forEach((header) => { + this.styleGroupHeader(header); + }); + + this._propertiesPane.groupHeaders.forEach((header) => { + this.styleGroupHeader(header); + }); } public layout(dimension: DOM.Dimension) { @@ -214,6 +230,7 @@ export class Designer extends Disposable implements IThemable { this._tabbedPanel.clearTabs(); this._propertiesPane.clear(); this._inputDisposable?.dispose(); + this._groupHeaders = []; // Initialize with new input @@ -247,9 +264,7 @@ export class Designer extends Disposable implements IThemable { private initializeDesigner(): void { const view = this._input.view; if (view.components) { - view.components.forEach(component => { - this.createComponent(this._topContentContainer, component, component.propertyName, true, true); - }); + this.createComponents(this._topContentContainer, view.components, this._componentMap, this._groupHeaders, component => component.propertyName, true, false); } view.tabs.forEach(tab => { this._tabbedPanel.pushTab(this.createTabView(tab)); @@ -415,8 +430,8 @@ export class Designer extends Disposable implements IThemable { } private createTabView(tab: DesignerTab): IPanelTab { - const view = new DesignerTabPanelView(tab, (container, component, identifier) => { - return this.createComponent(container, component, identifier, true, false); + const view = new DesignerTabPanelView(tab, (container, components, identifierGetter) => { + return this.createComponents(container, components, this._componentMap, this._groupHeaders, identifierGetter, true, false); }); return { identifier: tab.title, @@ -487,7 +502,47 @@ export class Designer extends Disposable implements IThemable { this._supressEditProcessing = false; } - private createComponent(container: HTMLElement, componentDefinition: DesignerDataPropertyInfo, editIdentifier: DesignerEditIdentifier, addToComponentMap: boolean, setWidth: boolean): DesignerUIComponent { + private createComponents(container: HTMLElement, + components: DesignerDataPropertyInfo[], + componentMap: Map, + groupHeaders: HTMLElement[], + identifierGetter: (definition: DesignerDataPropertyInfo) => DesignerEditIdentifier, + setWidth: boolean, skipTableCreation: boolean = false): DesignerUIComponent[] { + const uiComponents = []; + const groupNames = []; + const componentsToCreate = skipTableCreation ? components.filter(component => component.componentType !== 'table') : components; + componentsToCreate.forEach(component => { + if (component.group && groupNames.indexOf(component.group) === -1) { + groupNames.push(component.group); + } + }); + + // only show groups when there are multiple of them. + if (groupNames.length < 2) { + componentsToCreate.forEach(component => { + uiComponents.push(this.createComponent(container, component, identifierGetter(component), componentMap, setWidth)); + }); + } else { + groupNames.forEach(group => { + const groupHeader = container.appendChild(DOM.$('div.full-row')); + groupHeaders.push(groupHeader); + this.styleGroupHeader(groupHeader); + groupHeader.innerText = group; + componentsToCreate.forEach(component => { + if (component.group === group) { + uiComponents.push(this.createComponent(container, component, identifierGetter(component), componentMap, setWidth)); + } + }); + }); + } + return uiComponents; + } + + private createComponent(container: HTMLElement, + componentDefinition: DesignerDataPropertyInfo, + editIdentifier: DesignerEditIdentifier, + componentMap: Map, + setWidth: boolean): DesignerUIComponent { let component: DesignerUIComponent; switch (componentDefinition.componentType) { case 'input': @@ -642,12 +697,11 @@ export class Designer extends Disposable implements IThemable { default: throw new Error(localize('tableDesigner.unknownComponentType', "The component type: {0} is not supported", componentDefinition.componentType)); } - if (addToComponentMap) { - this._componentMap.set(componentDefinition.propertyName, { - defintion: componentDefinition, - component: component - }); - } + componentMap.set(componentDefinition.propertyName, { + defintion: componentDefinition, + component: component + }); + this.styleComponent(component); return component; } diff --git a/src/sql/base/browser/ui/designer/designerPropertiesPane.ts b/src/sql/base/browser/ui/designer/designerPropertiesPane.ts index dea5b34525..e59ee36b7a 100644 --- a/src/sql/base/browser/ui/designer/designerPropertiesPane.ts +++ b/src/sql/base/browser/ui/designer/designerPropertiesPane.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CreateComponentFunc, DesignerUIComponent, SetComponentValueFunc } from 'sql/base/browser/ui/designer/designer'; -import { DesignerViewModel, DesignerEditIdentifier, DesignerDataPropertyInfo, InputBoxProperties, NameProperty } from 'sql/base/browser/ui/designer/interfaces'; +import { CreateComponentsFunc, DesignerUIComponent, SetComponentValueFunc } from 'sql/base/browser/ui/designer/designer'; +import { DesignerViewModel, DesignerDataPropertyInfo, InputBoxProperties, NameProperty } from 'sql/base/browser/ui/designer/interfaces'; import * as DOM from 'vs/base/browser/dom'; import { equals } from 'vs/base/common/objects'; import { localize } from 'vs/nls'; @@ -26,14 +26,23 @@ export class DesignerPropertiesPane { private _contentElement: HTMLElement; private _currentContext?: PropertiesPaneObjectContext; private _componentMap = new Map(); + private _groupHeaders: HTMLElement[] = []; - constructor(container: HTMLElement, private _createComponent: CreateComponentFunc, private _setComponentValue: SetComponentValueFunc, private _styleComponent: (component: DesignerUIComponent) => void) { + constructor(container: HTMLElement, private _createComponents: CreateComponentsFunc, private _setComponentValue: SetComponentValueFunc) { const titleContainer = container.appendChild(DOM.$('.title-container')); this._titleElement = titleContainer.appendChild(DOM.$('div')); this._contentElement = container.appendChild(DOM.$('.properties-content.components-grid')); this._titleElement.innerText = localize('tableDesigner.propertiesPaneTitle', "Properties"); } + public get groupHeaders(): HTMLElement[] { + return this._groupHeaders; + } + + public get componentMap(): Map { + return this._componentMap; + } + public get context(): PropertiesPaneObjectContext | undefined { return this._currentContext; } @@ -43,34 +52,21 @@ export class DesignerPropertiesPane { value.component.dispose(); }); this._componentMap.clear(); + this._groupHeaders = []; DOM.clearNode(this._contentElement); this._currentContext = undefined; } - public style() { - this._componentMap.forEach((value) => { - this._styleComponent(value.component); - }); - } - public show(item: ObjectInfo): void { if (!equals(item.context, this._currentContext)) { this.clear(); this._currentContext = item.context; - item.components.forEach((value) => { - // todo: handle table type in properties pane - if (value.componentType !== 'table') { - const editIdentifier: DesignerEditIdentifier = this._currentContext === 'root' ? value.propertyName : { - parentProperty: this._currentContext.parentProperty, - index: this._currentContext.index, - property: value.propertyName - }; - const component = this._createComponent(this._contentElement, value, editIdentifier); - this._componentMap.set(value.propertyName, { - component: component, - defintion: value - }); - } + this._createComponents(this._contentElement, item.components, (property) => { + return this._currentContext === 'root' ? property.propertyName : { + parentProperty: this._currentContext.parentProperty, + index: this._currentContext.index, + property: property.propertyName + }; }); } const name = (item.viewModel[NameProperty])?.value ?? ''; diff --git a/src/sql/base/browser/ui/designer/designerTabPanelView.ts b/src/sql/base/browser/ui/designer/designerTabPanelView.ts index 04051c1e2f..a7dc44452b 100644 --- a/src/sql/base/browser/ui/designer/designerTabPanelView.ts +++ b/src/sql/base/browser/ui/designer/designerTabPanelView.ts @@ -8,7 +8,7 @@ import { IPanelView } from 'sql/base/browser/ui/panel/panel'; import { Table } from 'sql/base/browser/ui/table/table'; import { Disposable } from 'vs/base/common/lifecycle'; import * as DOM from 'vs/base/browser/dom'; -import { CreateComponentFunc } from 'sql/base/browser/ui/designer/designer'; +import { CreateComponentsFunc } from 'sql/base/browser/ui/designer/designer'; const ButtonHeight = 30; const HorizontalPadding = 10; @@ -17,13 +17,13 @@ const VerticalPadding = 20; export class DesignerTabPanelView extends Disposable implements IPanelView { private _componentsContainer: HTMLElement; private _tables: Table[] = []; - constructor(private readonly _tab: DesignerTab, private _createComponent: CreateComponentFunc) { + constructor(private readonly _tab: DesignerTab, private _createComponents: CreateComponentsFunc) { super(); this._componentsContainer = DOM.$('.components-grid'); - this._tab.components.forEach(componentDefition => { - const component = this._createComponent(this._componentsContainer, componentDefition, componentDefition.propertyName); - if (componentDefition.componentType === 'table') { - this._tables.push(component as Table); + const uiComponents = this._createComponents(this._componentsContainer, this._tab.components, component => component.propertyName); + uiComponents.forEach(component => { + if (component instanceof Table) { + this._tables.push(component); } }); } diff --git a/src/sql/platform/theme/common/colorRegistry.ts b/src/sql/platform/theme/common/colorRegistry.ts index 2123baa5f9..8fd848c673 100644 --- a/src/sql/platform/theme/common/colorRegistry.ts +++ b/src/sql/platform/theme/common/colorRegistry.ts @@ -7,6 +7,9 @@ import { contrastBorder, registerColor } from 'vs/platform/theme/common/colorReg import { Color, RGBA } from 'vs/base/common/color'; import * as nls from 'vs/nls'; +// Common +export const GroupHeaderBackground = registerColor('groupHeaderBackground', { dark: '#252526', light: '#F3F3F3', hc: '#000000' }, nls.localize('groupHeaderBackground', "Background color of the group header.")); + // -- Welcome Page Colors export const tileBoxShadowColor = new Color(new RGBA(0, 1, 4, 0.13)); export const textShadow = new Color(new RGBA(0, 0, 0, 0.25)); diff --git a/src/sql/platform/theme/common/styler.ts b/src/sql/platform/theme/common/styler.ts index b3907d2f2e..7117c6c182 100644 --- a/src/sql/platform/theme/common/styler.ts +++ b/src/sql/platform/theme/common/styler.ts @@ -385,7 +385,8 @@ export function attachDesignerStyler(widget: any, themeService: IThemeService): tableStyles: tableStyles, checkboxStyles: checkboxStyles, buttonStyles: buttonStyles, - paneSeparator: cr.resolveColorValue(sqlcr.DesignerPaneSeparator, colorTheme) + paneSeparator: cr.resolveColorValue(sqlcr.DesignerPaneSeparator, colorTheme), + groupHeaderBackground: cr.resolveColorValue(sqlcr.GroupHeaderBackground, colorTheme) }); } diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts index cc7690e6a6..b80321c88f 100644 --- a/src/sql/workbench/browser/modal/optionsDialog.ts +++ b/src/sql/workbench/browser/modal/optionsDialog.ts @@ -29,6 +29,7 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ServiceOptionType } from 'sql/platform/connection/common/interfaces'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { GroupHeaderBackground } from 'sql/platform/theme/common/colorRegistry'; export interface IOptionsDialogOptions extends IModalOptions { cancelLabel?: string; @@ -99,7 +100,7 @@ export class OptionsDialog extends Modal { private updateTheme(theme: IColorTheme): void { const borderColor = theme.getColor(contrastBorder); const border = borderColor ? borderColor.toString() : ''; - const backgroundColor = theme.getColor(SIDE_BAR_BACKGROUND); + const backgroundColor = theme.getColor(GroupHeaderBackground); if (this._dividerBuilder) { this._dividerBuilder.style.borderTopWidth = border ? '1px' : ''; this._dividerBuilder.style.borderTopStyle = border ? 'solid' : '';