/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /* eslint-disable @typescript-eslint/no-floating-promises */ import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter } from 'vs/base/common/event'; import { deepClone, assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import * as vscode from 'vscode'; import * as azdata from 'azdata'; import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape, ExtHostModelViewTreeViewsShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType, ColumnSizingMode, ModelViewAction } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { firstIndex } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; import { onUnexpectedError } from 'vs/base/common/errors'; class ModelBuilderImpl implements azdata.ModelBuilder { private nextComponentId: number; private readonly _componentBuilders = new Map>(); constructor( private readonly _proxy: MainThreadModelViewShape, private readonly _handle: number, private readonly _extHostModelViewTree: ExtHostModelViewTreeViewsShape, private readonly _extension: IExtensionDescription, private readonly logService: ILogService ) { this.nextComponentId = 0; } navContainer(): azdata.ContainerBuilder { let id = this.getNextComponentId(); let container: GenericContainerBuilder = new GenericContainerBuilder(this._proxy, this._handle, ModelComponentTypes.NavContainer, id); this._componentBuilders.set(id, container); return container; } divContainer(): azdata.DivBuilder { let id = this.getNextComponentId(); let container = new DivContainerBuilder(this._proxy, this._handle, ModelComponentTypes.DivContainer, id); this._componentBuilders.set(id, container); return container; } flexContainer(): azdata.FlexBuilder { let id = this.getNextComponentId(); let container: GenericContainerBuilder = new GenericContainerBuilder(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id); this._componentBuilders.set(id, container); return container; } splitViewContainer(): azdata.SplitViewBuilder { let id = this.getNextComponentId(); let container: GenericContainerBuilder = new GenericContainerBuilder(this._proxy, this._handle, ModelComponentTypes.SplitViewContainer, id); this._componentBuilders.set(id, container); return container; } formContainer(): azdata.FormBuilder { let id = this.getNextComponentId(); let container = new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id, this); this._componentBuilders.set(id, container); return container; } toolbarContainer(): azdata.ToolbarBuilder { let id = this.getNextComponentId(); let container = new ToolbarContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Toolbar, id); this._componentBuilders.set(id, container); return container; } groupContainer(): azdata.GroupBuilder { let id = this.getNextComponentId(); let container = new GroupContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Group, id); this._componentBuilders.set(id, container); return container; } private cardDeprecationMessagePrinted = false; card(): azdata.ComponentBuilder { if (!this.cardDeprecationMessagePrinted) { this.logService.warn(`Extension '${this._extension.identifier.value}' is using card component which has been replaced by radioCardGroup. the card component will be removed in a future release.`); this.cardDeprecationMessagePrinted = true; } let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new CardWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } tree(): azdata.ComponentBuilder, azdata.TreeProperties> { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl, azdata.TreeProperties> = this.getComponentBuilder(new TreeComponentWrapper(this._extHostModelViewTree, this._proxy, this._handle, id, this._extension), id); this._componentBuilders.set(id, builder); return builder; } inputBox(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new InputBoxWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } text(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new TextComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } image(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new ImageComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } radioButton(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new RadioButtonWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } checkBox(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new CheckBoxWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } webView(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new WebViewWrapper(this._proxy, this._handle, id, this._extension.extensionLocation), id); this._componentBuilders.set(id, builder); return builder; } editor(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new EditorWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } diffeditor(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new DiffEditorWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } button(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new ButtonWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } separator(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new SeparatorWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } dropDown(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new DropDownWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } listBox(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new ListBoxWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } table(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new TableComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } declarativeTable(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new DeclarativeTableWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } dashboardWidget(widgetId: string): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id); this._componentBuilders.set(id, builder); return builder; } dashboardWebview(webviewId: string): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id), id); this._componentBuilders.set(id, builder); return builder; } loadingComponent(): azdata.LoadingComponentBuilder { let id = this.getNextComponentId(); let builder = new LoadingComponentBuilder(new LoadingComponentWrapper(this._proxy, this._handle, id)); this._componentBuilders.set(id, builder); return builder; } fileBrowserTree(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new FileBrowserTreeComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } dom(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new DomComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } hyperlink(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new HyperlinkComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } radioCardGroup(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new RadioCardGroupComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } tabbedPanel(): azdata.TabbedPanelComponentBuilder { let id = this.getNextComponentId(); let builder = new TabbedPanelComponentBuilder(new TabbedPanelComponentWrapper(this._proxy, this._handle, id)); this._componentBuilders.set(id, builder); return builder; } propertiesContainer(): azdata.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new PropertiesContainerComponentWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); return builder; } getComponentBuilder(component: ComponentWrapper, id: string): ComponentBuilderImpl { let componentBuilder: ComponentBuilderImpl = new ComponentBuilderImpl(component); this._componentBuilders.set(id, componentBuilder); return componentBuilder; } handleEvent(componentId: string, eventArgs: IComponentEventArgs): void { let eventHandler = this._componentBuilders.get(componentId); if (eventHandler) { eventHandler.handleEvent(eventArgs); } } public runCustomValidations(componentId: string): boolean { let component = this._componentBuilders.get(componentId).componentWrapper(); return component.runCustomValidations(); } private getNextComponentId(): string { return `component${this._handle}_${this.nextComponentId++}`; } } interface IWithEventHandler { handleEvent(eventArgs: IComponentEventArgs): void; } class ComponentBuilderImpl implements azdata.ComponentBuilder, IWithEventHandler { constructor(protected _component: ComponentWrapper) { _component.registerEvent(); } component(): T { return this._component; } componentWrapper(): ComponentWrapper { return this._component; } withProperties(properties: U): azdata.ComponentBuilder { // Keep any properties that may have been set during initial object construction this._component.properties = assign({}, this._component.properties, properties); return this; } withProps(properties: TPropertyBag): azdata.ComponentBuilder { this._component.properties = assign({}, this._component.properties, properties); return this; } withValidation(validation: (component: T) => boolean): azdata.ComponentBuilder { this._component.customValidations.push(validation); return this; } handleEvent(eventArgs: IComponentEventArgs) { this._component.onEvent(eventArgs); } } class ContainerBuilderImpl extends ComponentBuilderImpl implements azdata.ContainerBuilder { constructor(componentWrapper: ComponentWrapper) { super(componentWrapper); } withLayout(layout: TLayout): azdata.ContainerBuilder { this._component.layout = layout; return this; } withItems(components: azdata.Component[], itemLayout?: TItemLayout): azdata.ContainerBuilder { this._component.itemConfigs = components.map(item => { let componentWrapper = item as ComponentWrapper; return new InternalItemConfig(componentWrapper, itemLayout); }); return this; } } class GenericContainerBuilder extends ContainerBuilderImpl { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(new ComponentWrapper(proxy, handle, type, id)); } } class DivContainerBuilder extends ContainerBuilderImpl { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(new DivContainerWrapper(proxy, handle, type, id)); } } class FormContainerBuilder extends GenericContainerBuilder implements azdata.FormBuilder { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string, private _builder: ModelBuilderImpl) { super(proxy, handle, type, id); } withFormItems(components: (azdata.FormComponent | azdata.FormComponentGroup)[], itemLayout?: azdata.FormItemLayout): azdata.FormBuilder { this.addFormItems(components, itemLayout); return this; } private convertToItemConfig(formComponent: azdata.FormComponent, itemLayout?: azdata.FormItemLayout): InternalItemConfig { let componentWrapper = formComponent.component as ComponentWrapper; if (formComponent.required && componentWrapper) { componentWrapper.required = true; } if (formComponent.title && componentWrapper) { componentWrapper.ariaLabel = formComponent.title; if (componentWrapper instanceof LoadingComponentWrapper) { componentWrapper.component.ariaLabel = formComponent.title; let containedComponent = componentWrapper.component as any; if (containedComponent.required) { componentWrapper.required = containedComponent.required; } } } let actions: string[] = undefined; if (formComponent.actions) { actions = formComponent.actions.map(action => { let actionComponentWrapper = action as ComponentWrapper; return actionComponentWrapper.id; }); } return new InternalItemConfig(componentWrapper, assign({}, itemLayout || {}, { title: formComponent.title, actions: actions, isFormComponent: true, required: componentWrapper.required })); } private addComponentActions(formComponent: azdata.FormComponent, itemLayout?: azdata.FormItemLayout): void { if (formComponent.actions) { formComponent.actions.forEach(component => { let componentWrapper = component as ComponentWrapper; this._component.addItem(componentWrapper, itemLayout); }); } } private removeComponentActions(formComponent: azdata.FormComponent): void { if (formComponent.actions) { formComponent.actions.forEach(component => { let componentWrapper = component as ComponentWrapper; this._component.removeItem(componentWrapper); }); } } addFormItems(formComponents: Array, itemLayout?: azdata.FormItemLayout): void { formComponents.forEach(formComponent => { this.addFormItem(formComponent, itemLayout); }); } addFormItem(formComponent: azdata.FormComponent | azdata.FormComponentGroup, itemLayout?: azdata.FormItemLayout): void { this.insertFormItem(formComponent, undefined, itemLayout); } insertFormItem(formComponent: azdata.FormComponent | azdata.FormComponentGroup, index?: number, itemLayout?: azdata.FormItemLayout): void { let componentGroup = formComponent as azdata.FormComponentGroup; if (componentGroup && componentGroup.components !== undefined) { let labelComponent = this._builder.text().component(); labelComponent.value = componentGroup.title; this._component.addItem(labelComponent, { isGroupLabel: true }, index); let componentIndex = index ? index + 1 : undefined; componentGroup.components.forEach(component => { let layout = component.layout || itemLayout; let itemConfig = this.convertToItemConfig(component, layout); itemConfig.config.isInGroup = true; this._component.insertItem(component.component as ComponentWrapper, componentIndex, itemConfig.config); if (componentIndex) { componentIndex++; } this.addComponentActions(component, layout); }); } else { formComponent = formComponent as azdata.FormComponent; let itemImpl = this.convertToItemConfig(formComponent, itemLayout); this._component.addItem(formComponent.component as ComponentWrapper, itemImpl.config, index); this.addComponentActions(formComponent, itemLayout); } } removeFormItem(formComponent: azdata.FormComponent | azdata.FormComponentGroup): boolean { let componentGroup = formComponent as azdata.FormComponentGroup; let result: boolean = false; if (componentGroup && componentGroup.components !== undefined) { let firstComponent = componentGroup.components[0]; let index = firstIndex(this._component.itemConfigs, x => x.component.id === firstComponent.component.id); if (index !== -1) { result = this._component.removeItemAt(index - 1); } componentGroup.components.forEach(element => { this.removeComponentActions(element); this._component.removeItem(element.component); }); } else { formComponent = formComponent as azdata.FormComponent; if (formComponent) { result = this._component.removeItem(formComponent.component as ComponentWrapper); this.removeComponentActions(formComponent); } } return result; } } class GroupContainerBuilder extends ContainerBuilderImpl { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(new GroupContainerComponentWrapper(proxy, handle, type, id)); } } class ToolbarContainerBuilder extends GenericContainerBuilder implements azdata.ToolbarBuilder { withToolbarItems(components: azdata.ToolbarComponent[]): azdata.ContainerBuilder { this._component.itemConfigs = components.map(item => { return this.convertToItemConfig(item); }); return this; } private convertToItemConfig(toolbarComponent: azdata.ToolbarComponent): InternalItemConfig { let componentWrapper = toolbarComponent.component as ComponentWrapper; return new InternalItemConfig(componentWrapper, { title: toolbarComponent.title, toolbarSeparatorAfter: toolbarComponent.toolbarSeparatorAfter }); } addToolbarItems(toolbarComponent: Array): void { toolbarComponent.forEach(toolbarComponent => { this.addToolbarItem(toolbarComponent); }); } addToolbarItem(toolbarComponent: azdata.ToolbarComponent): void { let itemImpl = this.convertToItemConfig(toolbarComponent); this._component.addItem(toolbarComponent.component as ComponentWrapper, itemImpl.config); } } class TabbedPanelComponentBuilder extends ContainerBuilderImpl implements azdata.TabbedPanelComponentBuilder { withTabs(items: (azdata.Tab | azdata.TabGroup)[]): azdata.ContainerBuilder { this._component.itemConfigs = createFromTabs(items); return this; } } function createFromTabs(items: (azdata.Tab | azdata.TabGroup)[]): InternalItemConfig[] { const itemConfigs = []; items.forEach(item => { if (item && 'tabs' in item) { item.tabs.forEach(tab => { itemConfigs.push(toTabItemConfig(tab.content, tab.title, tab.id, item.title, tab.icon)); }); } else { const tab = item; itemConfigs.push(toTabItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon)); } }); return itemConfigs; } function toTabItemConfig(content: azdata.Component, title: string, id?: string, group?: string, icon?: string | URI | { light: string | URI; dark: string | URI }): InternalItemConfig { return new InternalItemConfig(content as ComponentWrapper, { title: title, group: group, id: id, icon: icon }); } class LoadingComponentBuilder extends ComponentBuilderImpl implements azdata.LoadingComponentBuilder { withItem(component: azdata.Component) { this.component().component = component; return this; } } class InternalItemConfig { constructor(private _component: ComponentWrapper, public config: any) { } public toIItemConfig(): IItemConfig { return { config: this.config, componentShape: this._component.toComponentShape() }; } public get component(): azdata.Component { return this._component; } } class ComponentWrapper implements azdata.Component { public properties: { [key: string]: any } = {}; public layout: any; public itemConfigs: InternalItemConfig[]; public customValidations: ((component: ThisType) => boolean)[] = []; private _valid: boolean = true; private _onValidityChangedEmitter = new Emitter(); public readonly onValidityChanged = this._onValidityChangedEmitter.event; private _onErrorEmitter = new Emitter(); public readonly onError: vscode.Event = this._onErrorEmitter.event; protected _emitterMap = new Map>(); constructor(protected readonly _proxy: MainThreadModelViewShape, protected readonly _handle: number, protected _type: ModelComponentTypes, protected _id: string ) { this.properties = {}; this.itemConfigs = []; } public get id(): string { return this._id; } public get type(): ModelComponentTypes { return this._type; } public get items(): azdata.Component[] { return this.itemConfigs.map(itemConfig => itemConfig.component); } public get enabled(): boolean { let isEnabled = this.properties['enabled']; return (isEnabled === undefined) ? true : isEnabled; } public set enabled(value: boolean) { this.setProperty('enabled', value); } public get height(): number | string { return this.properties['height']; } public set height(v: number | string) { this.setProperty('height', v); } public get width(): number | string { return this.properties['width']; } public set width(v: number | string) { this.setProperty('width', v); } public get required(): boolean { return this.properties['required']; } public set required(v: boolean) { this.setProperty('required', v); } public get display(): azdata.DisplayType { return this.properties['display']; } public set display(v: azdata.DisplayType) { this.setProperty('display', v); } public get ariaLabel(): string { return this.properties['ariaLabel']; } public set ariaLabel(v: string) { this.setProperty('ariaLabel', v); } public get ariaRole(): string { return this.properties['ariaRole']; } public set ariaRole(v: string) { this.setProperty('ariaRole', v); } public get ariaSelected(): boolean { return this.properties['ariaSelected']; } public set ariaSelected(v: boolean) { this.setProperty('ariaSelected', v); } public get ariaHidden(): boolean { return this.properties['ariaHidden']; } public set ariaHidden(v: boolean) { this.setProperty('ariaHidden', 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 { id: this.id, type: this.type, layout: this.layout, properties: this.properties, itemConfigs: this.itemConfigs ? this.itemConfigs.map(item => item.toIItemConfig()) : undefined }; } public clearItems(): Thenable { this.itemConfigs = []; return this._proxy.$clearContainer(this._handle, this.id); } public addItems(items: Array, itemLayout?: any): void { for (let item of items) { this.addItem(item, itemLayout); } } public removeItemAt(index: number): boolean { if (index >= 0 && index < this.itemConfigs.length) { let itemConfig = this.itemConfigs[index]; this._proxy.$removeFromContainer(this._handle, this.id, itemConfig.toIItemConfig()); this.itemConfigs.splice(index, 1); return true; } return false; } public removeItem(item: azdata.Component): boolean { let index = firstIndex(this.itemConfigs, c => c.component.id === item.id); if (index >= 0 && index < this.itemConfigs.length) { return this.removeItemAt(index); } return false; } public insertItem(item: azdata.Component, index: number, itemLayout?: any) { this.addItem(item, itemLayout, index); } public addItem(item: azdata.Component, itemLayout?: any, index?: number): void { let itemImpl = item as ComponentWrapper; if (!itemImpl) { throw new Error(nls.localize('unknownComponentType', "Unknown component type. Must use ModelBuilder to create objects")); } let config = new InternalItemConfig(itemImpl, itemLayout); if (index !== undefined && index >= 0 && index <= this.items.length) { this.itemConfigs.splice(index, 0, config); } else if (!index) { this.itemConfigs.push(config); } else { throw new Error(nls.localize('invalidIndex', "The index {0} is invalid.", index)); } this._proxy.$addToContainer(this._handle, this.id, config.toIItemConfig(), index).then(undefined, (err) => this.handleError(err)); } public setLayout(layout: any): Thenable { return this._proxy.$setLayout(this._handle, this.id, layout); } public setItemLayout(item: azdata.Component, itemLayout: any): boolean { const itemConfig = this.itemConfigs.find(c => c.component.id === item.id); if (itemConfig) { itemConfig.config = itemLayout; this._proxy.$setItemLayout(this._handle, this.id, itemConfig.toIItemConfig()).then(undefined, onUnexpectedError); } return false; } public updateProperties(properties: { [key: string]: any }): Thenable { this.properties = assign(this.properties, properties); return this.notifyPropertyChanged(); } public updateProperty(key: string, value: any): Thenable { return this.setProperty(key, value); } public updateCssStyles(cssStyles: { [key: string]: string }): Thenable { this.properties.CSSStyles = assign(this.properties.CSSStyles || {}, cssStyles); return this.notifyPropertyChanged(); } protected notifyPropertyChanged(): Thenable { return this._proxy.$setProperties(this._handle, this._id, this.properties); } public registerEvent(): Thenable { return this._proxy.$registerEvent(this._handle, this._id).then(() => true); } public onEvent(eventArgs: IComponentEventArgs) { if (eventArgs && eventArgs.eventType === ComponentEventType.PropertiesChanged) { this.properties = eventArgs.args; } else if (eventArgs && eventArgs.eventType === ComponentEventType.validityChanged) { this._valid = eventArgs.args; this._onValidityChangedEmitter.fire(this._valid); } else if (eventArgs) { let emitter = this._emitterMap.get(eventArgs.eventType); if (emitter) { emitter.fire(eventArgs.args); } } } protected setDataProvider(): Thenable { return this._proxy.$setDataProvider(this._handle, this._id); } protected async setProperty(key: string, value: any): Promise { if (!this.properties[key] || this.properties[key] !== value) { // Only notify the front end if a value has been updated this.properties[key] = value; return this.notifyPropertyChanged(); } return Promise.resolve(); } private handleError(err: Error): void { this._onErrorEmitter.fire(err); } public runCustomValidations(): boolean { let isValid = true; try { this.customValidations.forEach(validation => { if (!validation(this)) { isValid = false; } }); } catch (e) { isValid = false; } return isValid; } public validate() { return this._proxy.$validate(this._handle, this._id); } public get valid(): boolean { return this._valid; } public focus() { return this._proxy.$focus(this._handle, this._id); } public doAction(action: ModelViewAction, ...args: any[]): Thenable { return this._proxy.$doAction(this._handle, this._id, action, ...args); } } class ComponentWithIconWrapper extends ComponentWrapper { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(proxy, handle, type, id); } public get iconPath(): string | URI | { light: string | URI; dark: string | URI } { return this.properties['iconPath']; } public set iconPath(v: string | URI | { light: string | URI; dark: string | URI }) { this.setProperty('iconPath', v); } public get iconHeight(): string | number { return this.properties['iconHeight']; } public set iconHeight(v: string | number) { this.setProperty('iconHeight', v); } public get iconWidth(): string | number { return this.properties['iconWidth']; } public set iconWidth(v: string | number) { this.setProperty('iconWidth', v); } public get title(): string { return this.properties['title']; } public set title(v: string) { this.setProperty('title', v); } } class CardWrapper extends ComponentWrapper implements azdata.CardComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Card, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get label(): string { return this.properties['label']; } public set label(l: string) { this.setProperty('label', l); } public get value(): string { return this.properties['value']; } public set value(v: string) { this.setProperty('value', v); } public get selected(): boolean { return this.properties['selected']; } public set selected(v: boolean) { this.setProperty('selected', v); } public get cardType(): azdata.CardType { return this.properties['cardType']; } public set cardType(v: azdata.CardType) { this.setProperty('cardType', v); } public get actions(): azdata.ActionDescriptor[] { return this.properties['actions']; } public set actions(a: azdata.ActionDescriptor[]) { this.setProperty('actions', a); } public get iconPath(): string | URI | { light: string | URI; dark: string | URI } { return this.properties['iconPath']; } public set iconPath(v: string | URI | { light: string | URI; dark: string | URI }) { this.setProperty('iconPath', v); } public get iconHeight(): number | string { return this.properties['iconHeight']; } public set iconHeight(v: number | string) { this.setProperty('iconHeight', v); } public get iconWidth(): number | string { return this.properties['iconWidth']; } public set iconWidth(v: number | string) { this.setProperty('iconWidth', v); } public get onDidActionClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } public get onCardSelectedChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } } class InputBoxWrapper extends ComponentWrapper implements azdata.InputBoxComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.InputBox, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); this._emitterMap.set(ComponentEventType.onEnterKeyPressed, new Emitter()); } public get value(): string { return this.properties['value']; } public set value(v: string) { this.setProperty('value', v); } public get ariaLive(): string { return this.properties['ariaLive']; } public set ariaLive(v: string) { this.setProperty('ariaLive', v); } public get placeHolder(): string { return this.properties['placeHolder']; } public set placeHolder(v: string) { this.setProperty('placeHolder', v); } public get rows(): number { return this.properties['rows']; } public set rows(v: number) { this.setProperty('rows', v); } public get min(): number { return this.properties['min']; } public set min(v: number) { this.setProperty('min', v); } public get max(): number { return this.properties['max']; } public set max(v: number) { this.setProperty('max', v); } public get columns(): number { return this.properties['columns']; } public set columns(v: number) { this.setProperty('columns', v); } public get multiline(): boolean { return this.properties['multiline']; } public set multiline(v: boolean) { this.setProperty('multiline', v); } public get inputType(): azdata.InputBoxInputType { return this.properties['inputType']; } public set inputType(v: azdata.InputBoxInputType) { this.setProperty('inputType', v); } public get stopEnterPropagation(): boolean { return this.properties['stopEnterPropagation']; } public set stopEnterPropagation(v: boolean) { this.setProperty('stopEnterPropagation', v); } public get onTextChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } public get onEnterKeyPressed(): vscode.Event { const emitter = this._emitterMap.get(ComponentEventType.onEnterKeyPressed); return emitter && emitter.event; } } class CheckBoxWrapper extends ComponentWrapper implements azdata.CheckBoxComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.CheckBox, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } public get checked(): boolean { return this.properties['checked']; } public set checked(v: boolean) { this.setProperty('checked', v); } public get label(): string { return this.properties['label']; } public set label(v: string) { this.setProperty('label', v); } public get onChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } } class WebViewWrapper extends ComponentWrapper implements azdata.WebViewComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string, private _extensionLocation: URI) { super(proxy, handle, ModelComponentTypes.WebView, id); this.properties = { 'extensionLocation': this._extensionLocation }; this._emitterMap.set(ComponentEventType.onMessage, new Emitter()); } public get message(): any { return this.properties['message']; } public set message(v: any) { this.setProperty('message', v); } public get html(): string { return this.properties['html']; } public set html(html: string) { this.setProperty('html', html); } public get onMessage(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onMessage); return emitter && emitter.event; } public get options(): vscode.WebviewOptions { return this.properties['options']; } public set options(o: vscode.WebviewOptions) { this.setProperty('options', o); } } class DomComponentWrapper extends ComponentWrapper implements azdata.DomComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Dom, id); this.properties = {}; } public get html(): string { return this.properties['html']; } public set html(html: string) { this.setProperty('html', html); } } class EditorWrapper extends ComponentWrapper implements azdata.EditorComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Editor, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); this._emitterMap.set(ComponentEventType.onComponentCreated, new Emitter()); } public get content(): string { return this.properties['content']; } public set content(v: string) { this.setProperty('content', v); } public get languageMode(): string { return this.properties['languageMode']; } public set languageMode(v: string) { this.setProperty('languageMode', v); } public get editorUri(): string { return this.properties['editorUri']; } public get isAutoResizable(): boolean { return this.properties['isAutoResizable']; } public set isAutoResizable(v: boolean) { this.setProperty('isAutoResizable', v); } public get minimumHeight(): number { return this.properties['minimumHeight']; } public set minimumHeight(v: number) { this.setProperty('minimumHeight', v); } public get onContentChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } public get onEditorCreated(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onComponentCreated); return emitter && emitter.event; } } class DiffEditorWrapper extends ComponentWrapper implements azdata.DiffEditorComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.DiffEditor, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); this._emitterMap.set(ComponentEventType.onComponentCreated, new Emitter()); } public get contentLeft(): string { return this.properties['contentLeft']; } public set contentLeft(v: string) { this.setProperty('contentLeft', v); } public get contentRight(): string { return this.properties['contentRight']; } public set contentRight(v: string) { this.setProperty('contentRight', v); } public get languageMode(): string { return this.properties['languageMode']; } public set languageMode(v: string) { this.setProperty('languageMode', v); } public get editorUri(): string { return this.properties['editorUri']; } public get isAutoResizable(): boolean { return this.properties['isAutoResizable']; } public set isAutoResizable(v: boolean) { this.setProperty('isAutoResizable', v); } public get minimumHeight(): number { return this.properties['minimumHeight']; } public set minimumHeight(v: number) { this.setProperty('minimumHeight', v); } public get onContentChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } public get onEditorCreated(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onComponentCreated); return emitter && emitter.event; } public get editorUriLeft(): string { return this.properties['editorUriLeft']; } public set editorUriLeft(v: string) { this.setProperty('editorUriLeft', v); } public get editorUriRight(): string { return this.properties['editorUriRight']; } public set editorUriRight(v: string) { this.setProperty('editorUriRight', v); } public get title(): string { return this.properties['title']; } public set title(v: string) { this.setProperty('title', v); } } class RadioButtonWrapper extends ComponentWrapper implements azdata.RadioButtonComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.RadioButton, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get name(): string { return this.properties['name']; } public set name(v: string) { this.setProperty('name', v); } public get label(): string { return this.properties['label']; } public set label(v: string) { this.setProperty('label', v); } public get value(): string { return this.properties['value']; } public set value(v: string) { this.setProperty('value', v); } public get checked(): boolean { return this.properties['checked']; } public set checked(v: boolean) { this.setProperty('checked', v); } public get onDidClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } } class TextComponentWrapper extends ComponentWrapper implements azdata.TextComponentProperties { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Text, id); this.properties = {}; } public get value(): string { return this.properties['value']; } public set value(v: string) { this.setProperty('value', v); } public get title(): string { return this.properties['title']; } public set title(title: string) { this.setProperty('title', title); } } class ImageComponentWrapper extends ComponentWithIconWrapper implements azdata.ImageComponentProperties { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Image, id); this.properties = {}; } } class TableComponentWrapper extends ComponentWrapper implements azdata.TableComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Table, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onSelectedRowChanged, new Emitter()); this._emitterMap.set(ComponentEventType.onCellAction, new Emitter()); } public get data(): any[][] { return this.properties['data']; } public set data(v: any[][]) { this.setProperty('data', v); } public get columns(): string[] | azdata.TableColumn[] { return this.properties['columns']; } public set columns(v: string[] | azdata.TableColumn[]) { this.setProperty('columns', v); } public get fontSize(): number | string { return this.properties['fontSize']; } public set fontSize(size: number | string) { this.setProperty('fontSize', size); } public get selectedRows(): number[] { return this.properties['selectedRows']; } public set selectedRows(v: number[]) { this.setProperty('selectedRows', v); } public get forceFitColumns(): ColumnSizingMode { return this.properties['forceFitColumns']; } public set forceFitColunms(v: ColumnSizingMode) { this.setProperty('forceFitColumns', v); } public get title(): string { return this.properties['title']; } public set title(v: string) { this.setProperty('title', v); } public get ariaRowCount(): number { return this.properties['ariaRowCount']; } public set ariaRowCount(v: number) { this.setProperty('ariaRowCount', v); } public get ariaColumnCount(): number { return this.properties['ariaColumnCount']; } public set ariaColumnCount(v: number) { this.setProperty('ariaColumnCount', v); } public get moveFocusOutWithTab(): boolean { return this.properties['moveFocusOutWithTab']; } public set moveFocusOutWithTab(v: boolean) { this.setProperty('moveFocusOutWithTab', v); } public get updateCells(): azdata.TableCell[] { return this.properties['updateCells']; } public set updateCells(v: azdata.TableCell[]) { this.setProperty('updateCells', v); } public get onRowSelected(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onSelectedRowChanged); return emitter && emitter.event; } public get onCellAction(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onCellAction); return emitter && emitter.event; } } class DropDownWrapper extends ComponentWrapper implements azdata.DropDownComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.DropDown, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } public get value(): string | azdata.CategoryValue { let val = this.properties['value']; if (!this.editable && !val && this.values && this.values.length > 0) { val = this.values[0]; } return val; } public set value(v: string | azdata.CategoryValue) { this.setProperty('value', v); } public get values(): string[] | azdata.CategoryValue[] { return this.properties['values']; } public set values(v: string[] | azdata.CategoryValue[]) { this.setProperty('values', v); } public get editable(): boolean { return this.properties['editable']; } public set editable(v: boolean) { this.setProperty('editable', v); } public get fireOnTextChange(): boolean { return this.properties['fireOnTextChange']; } public set fireOnTextChange(v: boolean) { this.setProperty('fireOnTextChange', v); } public get onValueChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } } class DeclarativeTableWrapper extends ComponentWrapper implements azdata.DeclarativeTableComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.DeclarativeTable, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } public get data(): any[][] { return this.properties['data']; } public set data(v: any[][]) { this.setProperty('data', v); } public get columns(): azdata.DeclarativeTableColumn[] { return this.properties['columns']; } public set columns(v: azdata.DeclarativeTableColumn[]) { this.setProperty('columns', v); } public get onDataChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } protected notifyPropertyChanged(): Thenable { return this._proxy.$setProperties(this._handle, this._id, this.getPropertiesForMainThread()); } public toComponentShape(): IComponentShape { // Overridden to ensure we send the correct properties mapping. return { id: this.id, type: this.type, layout: this.layout, properties: this.getPropertiesForMainThread(), itemConfigs: this.itemConfigs ? this.itemConfigs.map(item => item.toIItemConfig()) : undefined }; } /** * Gets the properties map to send to the main thread. */ private getPropertiesForMainThread(): { [key: string]: string } { // This is necessary because we can't send the actual ComponentWrapper objects // and so map them into their IDs instead. We don't want to update the actual // data property though since the caller would still expect that to contain // the Component objects they created const properties = assign({}, this.properties); if (properties.data) { properties.data = properties.data.map((row: any[]) => row.map(cell => { if (cell instanceof ComponentWrapper) { // First ensure that we register the component using addItem // such that it gets added to the ModelStore. We don't want to // make the table component an actual container since that exposes // a lot of functionality we don't need. this.addItem(cell); return cell.id; } return cell; })); } return properties; } } class ListBoxWrapper extends ComponentWrapper implements azdata.ListBoxComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.ListBox, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onSelectedRowChanged, new Emitter()); } public get selectedRow(): number { return this.properties['selectedRow']; } public set selectedRow(v: number) { this.setProperty('selectedRow', v); } public get values(): string[] { return this.properties['values']; } public set values(v: string[]) { this.setProperty('values', v); } public get onRowSelected(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onSelectedRowChanged); return emitter && emitter.event; } } class ButtonWrapper extends ComponentWithIconWrapper implements azdata.ButtonComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Button, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get label(): string { return this.properties['label']; } public set label(v: string) { this.setProperty('label', v); } public get onDidClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } } class LoadingComponentWrapper extends ComponentWrapper implements azdata.LoadingComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.LoadingComponent, id); this.properties = {}; this.loading = true; } public get loading(): boolean { return this.properties['loading']; } public set loading(value: boolean) { this.setProperty('loading', value); } public get component(): azdata.Component { return this.items[0]; } public set component(value: azdata.Component) { this.addItem(value); } } class FileBrowserTreeComponentWrapper extends ComponentWrapper implements azdata.FileBrowserTreeComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.FileBrowserTree, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } public get ownerUri(): string { return this.properties['ownerUri']; } public set ownerUri(value: string) { this.setProperty('ownerUri', value); } public get onDidChange(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } } class SeparatorWrapper extends ComponentWrapper implements azdata.SeparatorComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Separator, id); } } class DivContainerWrapper extends ComponentWrapper implements azdata.DivContainer { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(proxy, handle, type, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get overflowY(): string { return this.properties['overflowY']; } public set overflowY(value: string) { this.setProperty('overflowY', value); } public get yOffsetChange(): number { return this.properties['yOffsetChange']; } public set yOffsetChange(value: number) { this.setProperty('yOffsetChange', value); } public get onDidClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } } class TreeComponentWrapper extends ComponentWrapper implements azdata.TreeComponent { constructor( private _extHostModelViewTree: ExtHostModelViewTreeViewsShape, proxy: MainThreadModelViewShape, handle: number, id: string, private _extension: IExtensionDescription) { super(proxy, handle, ModelComponentTypes.TreeComponent, id); this.properties = {}; } public registerDataProvider(dataProvider: azdata.TreeComponentDataProvider): azdata.TreeComponentView { this.setDataProvider(); return this._extHostModelViewTree.$createTreeView(this._handle, this.id, { treeDataProvider: dataProvider }, this._extension); } public get withCheckbox(): boolean { return this.properties['withCheckbox']; } public set withCheckbox(v: boolean) { this.setProperty('withCheckbox', v); } } class HyperlinkComponentWrapper extends ComponentWrapper implements azdata.HyperlinkComponentProperties { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.Hyperlink, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get label(): string { return this.properties['label']; } public set label(v: string) { this.setProperty('label', v); } public get url(): string { return this.properties['url']; } public set url(v: string) { this.setProperty('url', v); } public get onDidClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } } class RadioCardGroupComponentWrapper extends ComponentWrapper implements azdata.RadioCardGroupComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.RadioCardGroup, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get iconWidth(): string | undefined { return this.properties['iconWidth']; } public set iconWidth(v: string | undefined) { this.setProperty('iconWidth', v); } public get iconHeight(): string | undefined { return this.properties['iconHeight']; } public set iconHeight(v: string | undefined) { this.setProperty('iconHeight', v); } public get cardWidth(): string | undefined { return this.properties['cardWidth']; } public set cardWidth(v: string | undefined) { this.setProperty('cardWidth', v); } public get cardHeight(): string | undefined { return this.properties['cardHeight']; } public set cardHeight(v: string | undefined) { this.setProperty('cardHeight', v); } public get cards(): azdata.RadioCard[] { return this.properties['cards']; } public set cards(v: azdata.RadioCard[]) { this.setProperty('cards', v); } public get selectedCardId(): string | undefined { return this.properties['selectedCardId']; } public set selectedCardId(v: string | undefined) { this.setProperty('selectedCardId', v); } public get orientation(): azdata.Orientation | undefined { return this.properties['orientation']; } public set orientation(orientation: azdata.Orientation | undefined) { this.setProperty('orientation', orientation); } public get onSelectionChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } public get onLinkClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } } class TabbedPanelComponentWrapper extends ComponentWrapper implements azdata.TabbedPanelComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.TabbedPanel, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } updateTabs(tabs: (azdata.Tab | azdata.TabGroup)[]): void { const itemConfigs = createFromTabs(tabs); // Go through all of the tabs and either update their layout if they already exist // or add them if they don't. // We do not currently support reordering or removing tabs. itemConfigs.forEach(newItemConfig => { const existingTab = this.itemConfigs.find(itemConfig => newItemConfig.config.id === itemConfig.config.id); if (existingTab) { this.setItemLayout(existingTab.component, newItemConfig.config); } else { this.addItem(newItemConfig.component, newItemConfig.config); } }); } public selectTab(id: string): void { this.doAction(ModelViewAction.SelectTab, id); } public get onTabChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } } class PropertiesContainerComponentWrapper extends ComponentWrapper implements azdata.PropertiesContainerComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { super(proxy, handle, ModelComponentTypes.PropertiesContainer, id); this.properties = {}; } public get propertyItems(): azdata.PropertiesContainerItem[] { return this.properties['propertyItems']; } public set propertyItems(v: azdata.PropertiesContainerItem[]) { this.setProperty('propertyItems', v); } public get loading(): boolean { return this.properties['loading']; } public set loading(v: boolean) { this.setProperty('loading', v); } } class GroupContainerComponentWrapper extends ComponentWrapper implements azdata.GroupContainer { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(proxy, handle, type, id); this.properties = {}; } public get collapsed(): boolean { return this.properties['collapsed']; } public set collapsed(v: boolean) { this.setProperty('collapsed', v); } } class ModelViewImpl implements azdata.ModelView { public onClosedEmitter = new Emitter(); private _onValidityChangedEmitter = new Emitter(); public readonly onValidityChanged = this._onValidityChangedEmitter.event; private _modelBuilder: ModelBuilderImpl; private _component: azdata.Component; constructor( private readonly _proxy: MainThreadModelViewShape, private readonly _handle: number, private readonly _connection: azdata.connection.Connection, private readonly _serverInfo: azdata.ServerInfo, private readonly _extHostModelViewTree: ExtHostModelViewTreeViewsShape, _extension: IExtensionDescription, logService: ILogService ) { this._modelBuilder = new ModelBuilderImpl(this._proxy, this._handle, this._extHostModelViewTree, _extension, logService); } public get onClosed(): vscode.Event { return this.onClosedEmitter.event; } public get connection(): azdata.connection.Connection { return deepClone(this._connection); } public get serverInfo(): azdata.ServerInfo { return deepClone(this._serverInfo); } public get modelBuilder(): azdata.ModelBuilder { return this._modelBuilder; } public get valid(): boolean { return this._component.valid; } public handleEvent(componentId: string, eventArgs: IComponentEventArgs): void { this._modelBuilder.handleEvent(componentId, eventArgs); } public initializeModel(component: T): Thenable { component.onValidityChanged(valid => this._onValidityChangedEmitter.fire(valid)); this._component = component; let componentImpl = component as ComponentWrapper; if (!componentImpl) { return Promise.reject(nls.localize('unknownConfig', "Unkown component configuration, must use ModelBuilder to create a configuration object")); } return this._proxy.$initializeModel(this._handle, componentImpl.toComponentShape()); } public validate(): Thenable { return this._proxy.$validate(this._handle, this._component.id); } public runCustomValidations(componentId: string): boolean { return this._modelBuilder.runCustomValidations(componentId); } } export class ExtHostModelView implements ExtHostModelViewShape { private readonly _proxy: MainThreadModelViewShape; private readonly _modelViews = new Map(); private readonly _handlers = new Map void>(); private readonly _handlerToExtension = new Map(); constructor( _mainContext: IMainContext, private _extHostModelViewTree: ExtHostModelViewTreeViewsShape, private readonly logService: ILogService ) { this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadModelView); } $onClosed(handle: number): void { const view = this._modelViews.get(handle); view.onClosedEmitter.fire(undefined); this._modelViews.delete(handle); } $registerProvider(widgetId: string, handler: (webview: azdata.ModelView) => void, extension: IExtensionDescription): void { this._handlers.set(widgetId, handler); this._handlerToExtension.set(widgetId, extension); this._proxy.$registerProvider(widgetId); } $registerWidget(handle: number, id: string, connection: azdata.connection.Connection, serverInfo: azdata.ServerInfo): void { let extension = this._handlerToExtension.get(id); let view = new ModelViewImpl(this._proxy, handle, connection, serverInfo, this._extHostModelViewTree, extension, this.logService); this._modelViews.set(handle, view); this._handlers.get(id)(view); } $handleEvent(handle: number, componentId: string, eventArgs: IComponentEventArgs): void { const view = this._modelViews.get(handle); if (view) { view.handleEvent(componentId, eventArgs); } } $runCustomValidations(handle: number, componentId: string): Thenable { const view = this._modelViews.get(handle); return Promise.resolve(view.runCustomValidations(componentId)); } }