/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter } from 'vs/base/common/event'; import { deepClone } 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/node/sqlExtHost.protocol'; import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; class ModelBuilderImpl implements azdata.ModelBuilder { private nextComponentId: number; private readonly _componentBuilders = new Map>(); constructor( private readonly _proxy: MainThreadModelViewShape, private readonly _handle: number, private readonly _mainContext: IMainContext, private readonly _extHostModelViewTree: ExtHostModelViewTreeViewsShape, private readonly _extension: IExtensionDescription ) { 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: GenericContainerBuilder = new GenericContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Group, id); this._componentBuilders.set(id, container); return container; } card(): azdata.ComponentBuilder { 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> { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl> = 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; } 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; } 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; } 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 = Object.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; } let actions: string[] = undefined; if (formComponent.actions) { actions = formComponent.actions.map(action => { let actionComponentWrapper = action as ComponentWrapper; return actionComponentWrapper.id; }); } return new InternalItemConfig(componentWrapper, Object.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 = this._component.itemConfigs.findIndex(x => x.component.id === firstComponent.component.id); if (index) { 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 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 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 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 = this.itemConfigs.findIndex(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', 'Unkown 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 is invalid.')); } 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 updateProperties(properties: { [key: string]: any }): Thenable { this.properties = Object.assign(this.properties, properties); return this.notifyPropertyChanged(); } public updateProperty(key: string, value: any): Thenable { return this.setProperty(key, value); } 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; } } class ContainerWrapper extends ComponentWrapper implements azdata.Container { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(proxy, handle, type, id); } } 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()); 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()); } public get value(): string { return this.properties['value']; } public set value(v: string) { this.setProperty('value', v); } public get ariaLabel(): string { return this.properties['ariaLabel']; } public set ariaLabel(v: string) { this.setProperty('ariaLabel', 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 onTextChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); 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); } } 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); } } 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 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 (!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; } } 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 ComponentWrapper 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 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); } 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 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 = {}; } 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); } } 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 mainContext: IMainContext, private readonly _extHostModelViewTree: ExtHostModelViewTreeViewsShape, _extension: IExtensionDescription ) { this._modelBuilder = new ModelBuilderImpl(this._proxy, this._handle, this.mainContext, this._extHostModelViewTree, _extension); } 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( private _mainContext: IMainContext, private _extHostModelViewTree: ExtHostModelViewTreeViewsShape ) { 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._mainContext, this._extHostModelViewTree, extension); 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)); } }