Layering of everything else but query (#5085)

* layer profiler and edit data

* relayering everything but query

* fix css import

* readd qp

* fix script src

* fix hygiene
This commit is contained in:
Anthony Dresser
2019-04-18 01:28:43 -07:00
committed by GitHub
parent ddd89fc52a
commit 9c0e56d640
170 changed files with 265 additions and 357 deletions

View File

@@ -0,0 +1,178 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/button';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentWithIconBase } from 'sql/workbench/electron-browser/modelComponents/componentWithIconBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
import { SIDE_BAR_BACKGROUND, SIDE_BAR_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry';
import { Button } from 'sql/base/browser/ui/button/button';
import { Color } from 'vs/base/common/color';
@Component({
selector: 'modelview-button',
template: `
<div>
<label for={{this.label}}>
<div #input style="width: 100%">
<input #fileInput *ngIf="this.isFile === true" id={{this.label}} type="file" accept=".sql" style="display: none">
</div>
</label>
</div>
`
})
export default class ButtonComponent extends ComponentWithIconBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _button: Button;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
@ViewChild('fileInput', { read: ElementRef }) private _fileInputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
this._button = new Button(this._inputContainer.nativeElement);
this._register(this._button);
this._register(attachButtonStyler(this._button, this.themeService, {
buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND, buttonForeground: SIDE_BAR_TITLE_FOREGROUND
}));
this._register(this._button.onDidClick(e => {
if (this._fileInputContainer) {
const self = this;
this._fileInputContainer.nativeElement.onchange = () => {
let file = self._fileInputContainer.nativeElement.files[0];
let reader = new FileReader();
reader.onload = (e) => {
let text = (<FileReader>e.target).result;
self.fileContent = text.toString();
self.fireEvent({
eventType: ComponentEventType.onDidClick,
args: self.fileContent
});
};
reader.readAsText(file);
};
} else {
this.fireEvent({
eventType: ComponentEventType.onDidClick,
args: e
});
}
}));
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._button.enabled = this.enabled;
this._button.label = this.label;
this._button.title = this.title;
if (this.width) {
this._button.setWidth(this.convertSize(this.width.toString()));
}
if (this.height) {
this._button.setWidth(this.convertSize(this.height.toString()));
}
this.updateIcon();
}
protected updateIcon() {
if (this.iconPath) {
if (!this._iconClass) {
super.updateIcon();
this._button.icon = this._iconClass + ' icon';
// Styling for icon button
this._register(attachButtonStyler(this._button, this.themeService, {
buttonBackground: Color.transparent.toString(),
buttonHoverBackground: Color.transparent.toString(),
buttonFocusOutline: focusBorder,
buttonForeground: foreground
}));
} else {
super.updateIcon();
}
}
}
// CSS-bound properties
private get label(): string {
return this.getPropertyOrDefault<azdata.ButtonProperties, string>((props) => props.label, '');
}
private set label(newValue: string) {
this.setPropertyFromUI<azdata.ButtonProperties, string>(this.setValueProperties, newValue);
}
private get isFile(): boolean {
return this.getPropertyOrDefault<azdata.ButtonProperties, boolean>((props) => props.isFile, false);
}
private set isFile(newValue: boolean) {
this.setPropertyFromUI<azdata.ButtonProperties, boolean>(this.setFileProperties, newValue);
}
private get fileContent(): string {
return this.getPropertyOrDefault<azdata.ButtonProperties, string>((props) => props.fileContent, '');
}
private set fileContent(newValue: string) {
this.setPropertyFromUI<azdata.ButtonProperties, string>(this.setFileContentProperties, newValue);
}
private setFileContentProperties(properties: azdata.ButtonProperties, fileContent: string): void {
properties.fileContent = fileContent;
}
private setValueProperties(properties: azdata.ButtonProperties, label: string): void {
properties.label = label;
}
private setFileProperties(properties: azdata.ButtonProperties, isFile: boolean): void {
properties.isFile = isFile;
}
private get title(): string {
return this.getPropertyOrDefault<azdata.ButtonProperties, string>((props) => props.title, '');
}
private set title(newValue: string) {
this.setPropertyFromUI<azdata.ButtonProperties, string>((properties, title) => { properties.title = title; }, newValue);
}
}

View File

@@ -0,0 +1,53 @@
<div *ngIf="label" [class]="getClass()" (click)="onCardClick()" (mouseover)="onCardHoverChanged($event)"
(mouseout)="onCardHoverChanged($event)" tabIndex="0">
<ng-container *ngIf="isVerticalButton || isDetailsCard">
<span *ngIf="hasStatus" class="card-status">
<div class="status-content" [style.backgroundColor]="statusColor"></div>
</span>
<span *ngIf="showRadioButton" class="selection-indicator-container">
<div *ngIf="showAsSelected" class="selection-indicator"></div>
</span>
<ng-container *ngIf="isVerticalButton">
<div class="card-vertical-button">
<div *ngIf="iconPath" class="iconContainer">
<div [class]="iconClass" [style.maxWidth]="iconWidth" [style.maxHeight]="iconHeight"></div>
</div>
<h4 class="card-label">{{label}}</h4>
<div *ngFor="let desc of descriptions">
<div class="list-item-description">{{desc}}</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="isDetailsCard">
<div class="card-content">
<h4 class="card-label">{{label}}</h4>
<p class="card-value">{{value}}</p>
<span *ngIf="actions">
<table class="model-table">
<tr *ngFor="let action of actions">
<td class="table-row">{{action.label}}</td>
<td *ngIf="action.actionTitle" class="table-row">
<a class="pointer prominent"
(click)="onDidActionClick(action)">{{action.actionTitle}}</a>
</td>
</tr>
</table>
</span>
</div>
</ng-container>
</ng-container>
<ng-container *ngIf="isListItemCard">
<div class="list-item-content">
<div>
<div [class]="iconClass">{{label}}</div>
<div *ngFor="let desc of descriptions">
<div class="list-item-description">{{desc}}</div>
</div>
<span *ngIf="showRadioButton" class="selection-indicator-container">
<div *ngIf="showAsSelected" class="selection-indicator"></div>
</span>
</div>
</div>
</ng-container>
</div>

View File

@@ -0,0 +1,187 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/card';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy
} from '@angular/core';
import * as azdata from 'azdata';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { ComponentWithIconBase } from 'sql/workbench/electron-browser/modelComponents/componentWithIconBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { StatusIndicator, CardProperties, ActionDescriptor } from 'sql/workbench/api/common/sqlExtHostTypes';
@Component({
templateUrl: decodeURI(require.toUrl('sql/workbench/electron-browser/modelComponents/card.component.html'))
})
export default class CardComponent extends ComponentWithIconBase implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private backgroundColor: string;
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
}
ngOnDestroy(): void {
this.baseDestroy();
}
private _defaultBorderColor = 'rgb(214, 214, 214)';
private _hasFocus: boolean;
public onCardClick() {
if (this.selectable) {
this.selected = !this.selected;
this._changeRef.detectChanges();
this.fireEvent({
eventType: ComponentEventType.onDidClick,
args: this.selected
});
}
}
public getBorderColor() {
if (this.selectable && this.selected || this._hasFocus) {
return 'Blue';
} else {
return this._defaultBorderColor;
}
}
public getClass(): string {
let cardClass = this.isListItemCard ? 'model-card-list-item' : 'model-card';
return (this.selectable && this.selected || this._hasFocus) ? `${cardClass} selected` :
`${cardClass} unselected`;
}
public onCardHoverChanged(event: any) {
if (this.selectable) {
this._hasFocus = event.type === 'mouseover';
this._changeRef.detectChanges();
}
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this.updateIcon();
}
public get iconClass(): string {
if (this.isListItemCard) {
return this._iconClass + ' icon' + ' list-item-icon';
}
else {
return this._iconClass + ' icon' + ' cardIcon';
}
}
private get selectable(): boolean {
return this.enabled && (this.cardType === 'VerticalButton' || this.cardType === 'ListItem');
}
// CSS-bound properties
public get label(): string {
return this.getPropertyOrDefault<CardProperties, string>((props) => props.label, '');
}
public get value(): string {
return this.getPropertyOrDefault<CardProperties, string>((props) => props.value, '');
}
public get cardType(): string {
return this.getPropertyOrDefault<CardProperties, string>((props) => props.cardType, 'Details');
}
public get selected(): boolean {
return this.getPropertyOrDefault<azdata.CardProperties, boolean>((props) => props.selected, false);
}
public set selected(newValue: boolean) {
this.setPropertyFromUI<azdata.CardProperties, boolean>((props, value) => props.selected = value, newValue);
}
public get isDetailsCard(): boolean {
return !this.cardType || this.cardType === 'Details';
}
public get isListItemCard(): boolean {
return !this.cardType || this.cardType === 'ListItem';
}
public get isVerticalButton(): boolean {
return this.cardType === 'VerticalButton';
}
public get showRadioButton(): boolean {
return this.selectable && (this.selected || this._hasFocus);
}
public get showAsSelected(): boolean {
return this.selectable && this.selected;
}
public get descriptions(): string[] {
return this.getPropertyOrDefault<CardProperties, string[]>((props) => props.descriptions, []);
}
public get actions(): ActionDescriptor[] {
return this.getPropertyOrDefault<CardProperties, ActionDescriptor[]>((props) => props.actions, []);
}
public hasStatus(): boolean {
let status = this.getPropertyOrDefault<CardProperties, StatusIndicator>((props) => props.status, StatusIndicator.None);
return status !== StatusIndicator.None;
}
public get statusColor(): string {
let status = this.getPropertyOrDefault<CardProperties, StatusIndicator>((props) => props.status, StatusIndicator.None);
switch (status) {
case StatusIndicator.Ok:
return 'green';
case StatusIndicator.Warning:
return 'orange';
case StatusIndicator.Error:
return 'red';
default:
return this.backgroundColor;
}
}
private updateTheme(theme: IColorTheme) {
this.backgroundColor = theme.getColor(colors.editorBackground, true).toString();
this._changeRef.detectChanges();
}
private onDidActionClick(action: ActionDescriptor): void {
this.fireEvent({
eventType: ComponentEventType.onDidClick,
args: action
});
}
}

View File

@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { Checkbox, ICheckboxOptions } from 'sql/base/browser/ui/checkbox/checkbox';
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
import { attachCheckboxStyler } from 'sql/platform/theme/common/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
@Component({
selector: 'modelview-checkbox',
template: `
<div #input [style.width]="getWidth()"></div>
`
})
export default class CheckBoxComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _input: Checkbox;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef, ) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
let inputOptions: ICheckboxOptions = {
label: ''
};
this._input = new Checkbox(this._inputContainer.nativeElement, inputOptions);
this._register(this._input);
this._register(this._input.onChange(e => {
this.checked = this._input.checked;
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: e
});
}));
this._register(attachCheckboxStyler(this._input, this.themeService));
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._input.checked = this.checked;
this._input.label = this.label;
if (this.enabled) {
this._input.enable();
} else {
this._input.disable();
}
}
// CSS-bound properties
public get checked(): boolean {
return this.getPropertyOrDefault<azdata.CheckBoxProperties, boolean>((props) => props.checked, false);
}
public set checked(newValue: boolean) {
this.setPropertyFromUI<azdata.CheckBoxProperties, boolean>((properties, value) => { properties.checked = value; }, newValue);
}
private get label(): string {
return this.getPropertyOrDefault<azdata.CheckBoxProperties, string>((props) => props.label, '');
}
private set label(newValue: string) {
this.setPropertyFromUI<azdata.CheckBoxProperties, string>((properties, label) => { properties.label = label; }, newValue);
}
}

View File

@@ -0,0 +1,325 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/flexContainer';
import {
ChangeDetectorRef, ViewChildren, ElementRef, OnDestroy, OnInit, QueryList
} from '@angular/core';
import * as types from 'vs/base/common/types';
import { IComponent, IComponentDescriptor, IModelStore, IComponentEventArgs, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import * as azdata from 'azdata';
import { Emitter } from 'vs/base/common/event';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { ModelComponentWrapper } from 'sql/workbench/electron-browser/modelComponents/modelComponentWrapper.component';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI };
export class ItemDescriptor<T> {
constructor(public descriptor: IComponentDescriptor, public config: T) { }
}
export abstract class ComponentBase extends Disposable implements IComponent, OnDestroy, OnInit {
protected properties: { [key: string]: any; } = {};
private _valid: boolean = true;
protected _validations: (() => boolean | Thenable<boolean>)[] = [];
private _eventQueue: IComponentEventArgs[] = [];
private _CSSStyles: { [key: string]: string } = {};
constructor(
protected _changeRef: ChangeDetectorRef,
protected _el: ElementRef) {
super();
}
/// IComponent implementation
abstract descriptor: IComponentDescriptor;
abstract modelStore: IModelStore;
protected _onEventEmitter = new Emitter<IComponentEventArgs>();
public layout(): void {
if (!this._changeRef['destroyed']) {
this._changeRef.detectChanges();
}
}
protected baseInit(): void {
if (this.modelStore) {
this.modelStore.registerComponent(this);
this._validations.push(() => this.modelStore.validate(this));
}
}
abstract ngOnInit(): void;
protected baseDestroy(): void {
if (this.modelStore) {
this.modelStore.unregisterComponent(this);
}
this.dispose();
}
ngOnDestroy(): void {
this.dispose();
}
abstract setLayout(layout: any): void;
getHtml(): any {
return this._el.nativeElement;
}
public setDataProvider(handle: number, componentId: string, context: any): void {
}
public refreshDataProvider(item: any): void {
}
public updateStyles() {
const element = (<HTMLElement>this._el.nativeElement);
for (const style in this.CSSStyles) {
element.style[style] = this.CSSStyles[style];
}
}
public setProperties(properties: { [key: string]: any; }): void {
if (!properties) {
this.properties = {};
}
this.properties = properties;
if (this.CSSStyles !== this._CSSStyles) {
this.updateStyles();
}
this.layout();
this.validate();
}
// Helper Function to update single property
public updateProperty(key: string, value: any): void {
if (key) {
this.properties[key] = value;
if (this.CSSStyles !== this._CSSStyles) {
this.updateStyles();
}
this.layout();
this.validate();
}
}
protected getProperties<TPropertyBag>(): TPropertyBag {
return this.properties as TPropertyBag;
}
protected getPropertyOrDefault<TPropertyBag, TValue>(propertyGetter: (TPropertyBag) => TValue, defaultVal: TValue) {
let property = propertyGetter(this.getProperties<TPropertyBag>());
return types.isUndefinedOrNull(property) ? defaultVal : property;
}
protected setPropertyFromUI<TPropertyBag, TValue>(propertySetter: (TPropertyBag, TValue) => void, value: TValue) {
propertySetter(this.getProperties<TPropertyBag>(), value);
this.fireEvent({
eventType: ComponentEventType.PropertiesChanged,
args: this.getProperties()
});
this.validate();
}
public get enabled(): boolean {
let properties = this.getProperties();
let enabled = properties['enabled'];
if (enabled === undefined) {
enabled = true;
properties['enabled'] = enabled;
}
return <boolean>enabled;
}
public set enabled(value: boolean) {
let properties = this.getProperties();
properties['enabled'] = value;
this.setProperties(properties);
}
public get height(): number | string {
return this.getPropertyOrDefault<azdata.ComponentProperties, number | string>((props) => props.height, undefined);
}
public set height(newValue: number | string) {
this.setPropertyFromUI<azdata.ComponentProperties, number | string>((props, value) => props.height = value, newValue);
}
public get width(): number | string {
return this.getPropertyOrDefault<azdata.ComponentProperties, number | string>((props) => props.width, undefined);
}
public set width(newValue: number | string) {
this.setPropertyFromUI<azdata.ComponentProperties, number | string>((props, value) => props.width = value, newValue);
}
public get position(): string {
return this.getPropertyOrDefault<azdata.ComponentProperties, string>((props) => props.position, '');
}
public set position(newValue: string) {
this.setPropertyFromUI<azdata.ComponentProperties, string>((properties, position) => { properties.position = position; }, newValue);
}
public get CSSStyles(): { [key: string]: string } {
return this.getPropertyOrDefault<azdata.ComponentProperties, { [key: string]: string }>((props) => props.CSSStyles, {});
}
public set CSSStyles(newValue: { [key: string]: string }) {
this.setPropertyFromUI<azdata.ComponentProperties, { [key: string]: string }>((properties, CSSStyles) => { properties.CSSStyles = CSSStyles; }, newValue);
}
public convertSizeToNumber(size: number | string): number {
if (size && typeof (size) === 'string') {
if (size.toLowerCase().endsWith('px')) {
return +size.replace('px', '');
} else if (size.toLowerCase().endsWith('em')) {
return +size.replace('em', '') * 11;
}
} else if (!size) {
return 0;
}
return +size;
}
protected getWidth(): string {
return this.width ? this.convertSize(this.width) : '';
}
protected getHeight(): string {
return this.height ? this.convertSize(this.height) : '';
}
public convertSize(size: number | string, defaultValue?: string): string {
defaultValue = defaultValue || '';
if (types.isUndefinedOrNull(size)) {
return defaultValue;
}
let convertedSize: string = size ? size.toString() : defaultValue;
if (!convertedSize.toLowerCase().endsWith('px') && !convertedSize.toLowerCase().endsWith('%')) {
convertedSize = convertedSize + 'px';
}
return convertedSize;
}
public get valid(): boolean {
return this._valid;
}
public registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable {
if (this._eventQueue) {
while (this._eventQueue.length > 0) {
let event = this._eventQueue.pop();
handler(event);
}
this._eventQueue = undefined;
}
return this._onEventEmitter.event(handler);
}
protected fireEvent(event: IComponentEventArgs) {
this._onEventEmitter.fire(event);
if (this._eventQueue) {
this._eventQueue.push(event);
}
}
public validate(): Thenable<boolean> {
let validations = this._validations.map(validation => Promise.resolve(validation()));
return Promise.all(validations).then(values => {
let isValid = values.every(value => value === true);
if (this._valid !== isValid) {
this._valid = isValid;
this.fireEvent({
eventType: ComponentEventType.validityChanged,
args: this._valid
});
}
return isValid;
});
}
}
export abstract class ContainerBase<T> extends ComponentBase {
protected items: ItemDescriptor<T>[];
@ViewChildren(ModelComponentWrapper) protected _componentWrappers: QueryList<ModelComponentWrapper>;
constructor(
_changeRef: ChangeDetectorRef,
_el: ElementRef
) {
super(_changeRef, _el);
this.items = [];
this._validations.push(() => this.items.every(item => {
return this.modelStore.getComponent(item.descriptor.id).valid;
}));
}
/// IComponent container-related implementation
public addToContainer(componentDescriptor: IComponentDescriptor, config: any, index?: number): void {
if (this.items.some(item => item.descriptor.id === componentDescriptor.id && item.descriptor.type === componentDescriptor.type)) {
return;
}
if (index !== undefined && index !== null && index >= 0 && index < this.items.length) {
this.items.splice(index, 0, new ItemDescriptor(componentDescriptor, config));
} else if (!index) {
this.items.push(new ItemDescriptor(componentDescriptor, config));
} else {
throw new Error(nls.localize('invalidIndex', 'The index is invalid.'));
}
this.modelStore.eventuallyRunOnComponent(componentDescriptor.id, component => component.registerEventHandler(event => {
if (event.eventType === ComponentEventType.validityChanged) {
this.validate();
}
}));
this._changeRef.detectChanges();
return;
}
public removeFromContainer(componentDescriptor: IComponentDescriptor): boolean {
let index = this.items.findIndex(item => item.descriptor.id === componentDescriptor.id && item.descriptor.type === componentDescriptor.type);
if (index >= 0) {
this.items.splice(index, 1);
this._changeRef.detectChanges();
return true;
}
return false;
}
public clearContainer(): void {
this.items = [];
this._changeRef.detectChanges();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this.items.forEach(item => {
let component = this.modelStore.getComponent(item.descriptor.id);
if (component) {
component.enabled = this.enabled;
}
});
}
public layout(): void {
if (this._componentWrappers) {
this._componentWrappers.forEach(wrapper => {
wrapper.layout();
});
}
super.layout();
}
abstract setLayout(layout: any): void;
}

View File

@@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ChangeDetectorRef, ElementRef } from '@angular/core';
import { IComponentDescriptor } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import * as azdata from 'azdata';
import { URI } from 'vs/base/common/uri';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI };
export class ItemDescriptor<T> {
constructor(public descriptor: IComponentDescriptor, public config: T) { }
}
const ids = new IdGenerator('model-view-component-icon-');
export abstract class ComponentWithIconBase extends ComponentBase {
protected _iconClass: string;
protected _iconPath: IUserFriendlyIcon;
constructor(
changeRef: ChangeDetectorRef,
el: ElementRef, ) {
super(changeRef, el);
}
/// IComponent implementation
public get iconClass(): string {
return this._iconClass + ' icon';
}
protected updateIcon() {
if (this.iconPath && this.iconPath !== this._iconPath) {
this._iconPath = this.iconPath;
if (!this._iconClass) {
this._iconClass = ids.nextId();
}
removeCSSRulesContainingSelector(this._iconClass);
const icon = this.getLightIconPath(this.iconPath);
const iconDark = this.getDarkIconPath(this.iconPath) || icon;
createCSSRule(`.icon.${this._iconClass}`, `background-image: url("${icon}")`);
createCSSRule(`.vs-dark .icon.${this._iconClass}, .hc-black .icon.${this._iconClass}`, `background-image: url("${iconDark}")`);
this._changeRef.detectChanges();
}
}
private getLightIconPath(iconPath: IUserFriendlyIcon): string {
if (iconPath && iconPath['light']) {
return this.getIconPath(iconPath['light']);
} else {
return this.getIconPath(<string | URI>iconPath);
}
}
private getDarkIconPath(iconPath: IUserFriendlyIcon): string {
if (iconPath && iconPath['dark']) {
return this.getIconPath(iconPath['dark']);
}
return null;
}
private getIconPath(iconPath: string | URI): string {
if (typeof iconPath === 'string') {
return URI.file(iconPath).toString();
} else {
let uri = URI.revive(iconPath);
return uri.toString();
}
}
public getIconWidth(): string {
return this.convertSize(this.iconWidth, '40px');
}
public getIconHeight(): string {
return this.convertSize(this.iconHeight, '40px');
}
public get iconPath(): string | URI | { light: string | URI; dark: string | URI } {
return this.getPropertyOrDefault<azdata.ComponentWithIcon, IUserFriendlyIcon>((props) => props.iconPath, undefined);
}
public get iconHeight(): number | string {
return this.getPropertyOrDefault<azdata.ComponentWithIcon, number | string>((props) => props.iconHeight, '50px');
}
public get iconWidth(): number | string {
return this.getPropertyOrDefault<azdata.ComponentWithIcon, number | string>((props) => props.iconWidth, '50px');
}
ngOnDestroy(): void {
if (this._iconClass) {
removeCSSRulesContainingSelector(this._iconClass);
}
super.ngOnDestroy();
}
}

View File

@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import DivContainer from './divContainer.component';
import FlexContainer from './flexContainer.component';
import FormContainer from './formContainer.component';
import ToolbarContainer from './toolbarContainer.component';
import GroupContainer from './groupContainer.component';
import CardComponent from './card.component';
import InputBoxComponent from './inputbox.component';
import DropDownComponent from './dropdown.component';
import DeclarativeTableComponent from './declarativeTable.component';
import ListBoxComponent from './listbox.component';
import ButtonComponent from './button.component';
import CheckBoxComponent from './checkbox.component';
import TreeComponent from './tree.component';
import RadioButtonComponent from './radioButton.component';
import WebViewComponent from './webview.component';
import TableComponent from './table.component';
import TextComponent from './text.component';
import LoadingComponent from './loadingComponent.component';
import FileBrowserTreeComponent from './fileBrowserTree.component';
import EditorComponent from './editor.component';
import DiffEditorComponent from './diffeditor.component';
import DomComponent from './dom.component';
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
import HyperlinkComponent from 'sql/workbench/electron-browser/modelComponents/hyperlink.component';
import SplitViewContainer from 'sql/workbench/electron-browser/modelComponents/splitviewContainer.component';
export const DIV_CONTAINER = 'div-container';
registerComponentType(DIV_CONTAINER, ModelComponentTypes.DivContainer, DivContainer);
export const FLEX_CONTAINER = 'flex-container';
registerComponentType(FLEX_CONTAINER, ModelComponentTypes.FlexContainer, FlexContainer);
export const SPLITVIEW_CONTAINER = 'splitView-container';
registerComponentType(SPLITVIEW_CONTAINER, ModelComponentTypes.SplitViewContainer, SplitViewContainer);
export const FORM_CONTAINER = 'form-container';
registerComponentType(FORM_CONTAINER, ModelComponentTypes.Form, FormContainer);
export const TOOLBAR_CONTAINER = 'toolbar-container';
registerComponentType(TOOLBAR_CONTAINER, ModelComponentTypes.Toolbar, ToolbarContainer);
export const GROUP_CONTAINER = 'group-container';
registerComponentType(GROUP_CONTAINER, ModelComponentTypes.Group, GroupContainer);
export const CARD_COMPONENT = 'card-component';
registerComponentType(CARD_COMPONENT, ModelComponentTypes.Card, CardComponent);
export const INPUTBOX_COMPONENT = 'inputbox-component';
registerComponentType(INPUTBOX_COMPONENT, ModelComponentTypes.InputBox, InputBoxComponent);
export const DROPDOWN_COMPONENT = 'dropdown-component';
registerComponentType(DROPDOWN_COMPONENT, ModelComponentTypes.DropDown, DropDownComponent);
export const DECLARATIVETABLE_COMPONENT = 'declarativeTable-component';
registerComponentType(DECLARATIVETABLE_COMPONENT, ModelComponentTypes.DeclarativeTable, DeclarativeTableComponent);
export const LISTBOX_COMPONENT = 'lisbox-component';
registerComponentType(LISTBOX_COMPONENT, ModelComponentTypes.ListBox, ListBoxComponent);
export const BUTTON_COMPONENT = 'button-component';
registerComponentType(BUTTON_COMPONENT, ModelComponentTypes.Button, ButtonComponent);
export const CHECKBOX_COMPONENT = 'checkbox-component';
registerComponentType(CHECKBOX_COMPONENT, ModelComponentTypes.CheckBox, CheckBoxComponent);
export const RADIOBUTTON_COMPONENT = 'radiobutton-component';
registerComponentType(RADIOBUTTON_COMPONENT, ModelComponentTypes.RadioButton, RadioButtonComponent);
export const WEBVIEW_COMPONENT = 'webview-component';
registerComponentType(WEBVIEW_COMPONENT, ModelComponentTypes.WebView, WebViewComponent);
export const TEXT_COMPONENT = 'text-component';
registerComponentType(TEXT_COMPONENT, ModelComponentTypes.Text, TextComponent);
export const TABLE_COMPONENT = 'table-component';
registerComponentType(TABLE_COMPONENT, ModelComponentTypes.Table, TableComponent);
export const LOADING_COMPONENT = 'loading-component';
registerComponentType(LOADING_COMPONENT, ModelComponentTypes.LoadingComponent, LoadingComponent);
export const TREE_COMPONENT = 'tree-component';
registerComponentType(TREE_COMPONENT, ModelComponentTypes.TreeComponent, TreeComponent);
export const FILEBROWSERTREE_COMPONENT = 'filebrowsertree-component';
registerComponentType(FILEBROWSERTREE_COMPONENT, ModelComponentTypes.FileBrowserTree, FileBrowserTreeComponent);
export const EDITOR_COMPONENT = 'editor-component';
registerComponentType(EDITOR_COMPONENT, ModelComponentTypes.Editor, EditorComponent);
export const DIFF_EDITOR_COMPONENT = 'diff-editor-component';
registerComponentType(DIFF_EDITOR_COMPONENT, ModelComponentTypes.DiffEditor, DiffEditorComponent);
export const DOM_COMPONENT = 'dom-component';
registerComponentType(DOM_COMPONENT, ModelComponentTypes.Dom, DomComponent);
export const HYPERLINK_COMPONENT = 'hyperlink-component';
registerComponentType(HYPERLINK_COMPONENT, ModelComponentTypes.Hyperlink, HyperlinkComponent);

View File

@@ -0,0 +1,208 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/declarativeTable';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
export enum DeclarativeDataType {
string = 'string',
category = 'category',
boolean = 'boolean',
editableCategory = 'editableCategory'
}
@Component({
selector: 'modelview-declarativeTable',
template: `
<table role=grid aria-labelledby="ID_REF" #container *ngIf="columns" class="declarative-table" [style.height]="getHeight()">
<thead>
<ng-container *ngFor="let column of columns;let h = index">
<th class="declarative-table-header" tabindex="-1" role="button" aria-sort="none">{{column.displayName}}</th>
</ng-container>
</thead>
<ng-container *ngIf="data">
<ng-container *ngFor="let row of data;let r = index">
<tr class="declarative-table-row" >
<ng-container *ngFor="let cellData of row;let c = index">
<td class="declarative-table-cell" tabindex="-1" role="button" [style.width]="getColumnWidth(c)">
<checkbox *ngIf="isCheckBox(c)" label="" (onChange)="onCheckBoxChanged($event,r,c)" [enabled]="isControlEnabled(c)" [checked]="isChecked(r,c)"></checkbox>
<select-box *ngIf="isSelectBox(c)" [options]="GetOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="GetSelectedOptionDisplayName(r,c)"></select-box>
<editable-select-box *ngIf="isEditableSelectBox(c)" [options]="GetOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="GetSelectedOptionDisplayName(r,c)"></editable-select-box>
<input-box *ngIf="isInputBox(c)" [value]="cellData" (onDidChange)="onInputBoxChanged($event,r,c)"></input-box>
<ng-container *ngIf="isLabel(c)" >{{cellData}}</ng-container>
</td>
</ng-container>
</tr>
</ng-container>
</ng-container>
</table>
`
})
export default class DeclarativeTableComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
@ViewChild('container', { read: ElementRef }) private _tableContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
}
public validate(): Thenable<boolean> {
return super.validate().then(valid => {
return valid;
});
}
ngOnDestroy(): void {
this.baseDestroy();
}
private isCheckBox(cell: number): boolean {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return column.valueType === DeclarativeDataType.boolean;
}
private isControlEnabled(cell: number): boolean {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return !column.isReadOnly;
}
private isLabel(cell: number): boolean {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return column.isReadOnly && column.valueType === DeclarativeDataType.string;
}
private isChecked(row: number, cell: number): boolean {
let cellData = this.data[row][cell];
return cellData;
}
private onInputBoxChanged(e: string, row: number, cell: number): void {
this.onCellDataChanged(e, row, cell);
}
private onCheckBoxChanged(e: boolean, row: number, cell: number): void {
this.onCellDataChanged(e, row, cell);
}
private onSelectBoxChanged(e: ISelectData | string, row: number, cell: number): void {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
if (column.categoryValues) {
if (typeof e === 'string') {
let category = column.categoryValues.find(c => c.displayName === e);
if (category) {
this.onCellDataChanged(category.name, row, cell);
} else {
this.onCellDataChanged(e, row, cell);
}
} else {
this.onCellDataChanged(column.categoryValues[e.index].name, row, cell);
}
}
}
private onCellDataChanged(newValue: any, row: number, cell: number): void {
this.data[row][cell] = newValue;
this.data = this.data;
let newCellData: azdata.TableCell = {
row: row,
column: cell,
value: newValue
};
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: newCellData
});
}
private isSelectBox(cell: number): boolean {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return column.valueType === DeclarativeDataType.category;
}
private isEditableSelectBox(cell: number): boolean {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return column.valueType === DeclarativeDataType.editableCategory;
}
private isInputBox(cell: number): boolean {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return column.valueType === DeclarativeDataType.string && !column.isReadOnly;
}
private getColumnWidth(cell: number): string {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return this.convertSize(column.width, '30px');
}
private GetOptions(cell: number): string[] {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
return column.categoryValues ? column.categoryValues.map(x => x.displayName) : [];
}
private GetSelectedOptionDisplayName(row: number, cell: number): string {
let column: azdata.DeclarativeTableColumn = this.columns[cell];
let cellData = this.data[row][cell];
if (cellData && column.categoryValues) {
let category = column.categoryValues.find(v => v.name === cellData);
if (category) {
return category.displayName;
} else if (this.isEditableSelectBox(cell)) {
return cellData;
} else {
return undefined;
}
} else {
return '';
}
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
}
public get data(): any[][] {
return this.getPropertyOrDefault<azdata.DeclarativeTableProperties, any[]>((props) => props.data, []);
}
public set data(newValue: any[][]) {
this.setPropertyFromUI<azdata.DeclarativeTableProperties, any[][]>((props, value) => props.data = value, newValue);
}
public get columns(): azdata.DeclarativeTableColumn[] {
return this.getPropertyOrDefault<azdata.DeclarativeTableProperties, azdata.DeclarativeTableColumn[]>((props) => props.columns, []);
}
public set columns(newValue: azdata.DeclarativeTableColumn[]) {
this.setPropertyFromUI<azdata.DeclarativeTableProperties, azdata.DeclarativeTableColumn[]>((props, value) => props.columns = value, newValue);
}
}

View File

@@ -0,0 +1,230 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/editor';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy
} from '@angular/core';
import * as azdata from 'azdata';
import * as DOM from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SimpleProgressService } from 'vs/editor/standalone/browser/simpleServices';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
@Component({
template: `
<div *ngIf="_title">
<div class="modelview-diff-editor-title" style="width: 100%; height:100%; padding-left:3px !important; border: 1px solid #BFBDBD;">
{{_title}}
</div>
</div>`,
selector: 'modelview-diff-editor-component'
})
export default class DiffEditorComponent extends ComponentBase implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _editor: TextDiffEditor;
private _editorInput: DiffEditorInput;
private _editorModel: TextDiffEditorModel;
private _renderedContentLeft: string;
private _renderedContentRight: string;
private _languageMode: string;
private _isAutoResizable: boolean;
private _minimumHeight: number;
private _instancetiationService: IInstantiationService;
protected _title: string;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IModelService) private _modelService: IModelService,
@Inject(IModeService) private _modeService: IModeService
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
this._createEditor();
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
this.layout();
}));
}
private _createEditor(): void {
this._instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()]));
this._editor = this._instantiationService.createInstance(TextDiffEditor);
this._editor.reverseColoring();
this._editor.create(this._el.nativeElement);
this._editor.setVisible(true);
let uri1 = this.createUri('source');
this.editorUriLeft = uri1.toString();
let uri2 = this.createUri('target');
this.editorUriRight = uri2.toString();
let cancellationTokenSource = new CancellationTokenSource();
let editorinput1 = this._instantiationService.createInstance(UntitledEditorInput, uri1, false, 'plaintext', '', '');
let editorinput2 = this._instantiationService.createInstance(UntitledEditorInput, uri2, false, 'plaintext', '', '');
this._editorInput = this._instantiationService.createInstance(DiffEditorInput, 'MyEditor', 'My description', editorinput1, editorinput2, true);
this._editor.setInput(this._editorInput, undefined, cancellationTokenSource.token);
this._editorInput.resolve().then(model => {
this._editorModel = model as TextDiffEditorModel;
this.updateModel();
this.layout();
this.validate();
});
this._register(this._editor);
this._register(this._editorInput);
this._register(this._editorModel);
}
private createUri(input: string): URI {
let uri = URI.from({ scheme: Schemas.untitled, path: `${this.descriptor.type}-${this.descriptor.id}-${input}` });
return uri;
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public layout(): void {
let width: number = this.convertSizeToNumber(this.width);
let height: number = this.convertSizeToNumber(this.height);
if (this._isAutoResizable) {
height = Math.max(this._editor.maximumHeight, this._minimumHeight ? this._minimumHeight : 0);
}
this._editor.layout(new DOM.Dimension(
width && width > 0 ? width : DOM.getContentWidth(this._el.nativeElement),
height && height > 0 ? height : DOM.getContentHeight(this._el.nativeElement)));
let element = <HTMLElement>this._el.nativeElement;
element.style.position = this.position;
super.layout();
}
/// Editor Functions
private updateModel() {
if (this._editorModel) {
this._renderedContentLeft = this.contentLeft;
this._renderedContentRight = this.contentRight;
this._modelService.updateModel(this._editorModel.originalModel.textEditorModel, this._renderedContentLeft);
this._modelService.updateModel(this._editorModel.modifiedModel.textEditorModel, this._renderedContentRight);
}
}
private updateLanguageMode() {
if (this._editorModel && this._editor) {
this._languageMode = this.languageMode;
let languageSelection = this._modeService.create(this._languageMode);
this._modelService.setMode(this._editorModel.originalModel.textEditorModel, languageSelection);
this._modelService.setMode(this._editorModel.modifiedModel.textEditorModel, languageSelection);
}
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
if (this.contentLeft !== this._renderedContentLeft || this.contentRight !== this._renderedContentRight) {
this.updateModel();
}
if (this.languageMode !== this._languageMode) {
this.updateLanguageMode();
}
this._isAutoResizable = this.isAutoResizable;
this._minimumHeight = this.minimumHeight;
this._title = this.title;
this.layout();
this.validate();
}
// CSS-bound properties
public get contentLeft(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.contentLeft, undefined);
}
public set contentLeft(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, contentLeft) => { properties.contentLeft = contentLeft; }, newValue);
}
public get contentRight(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.contentRight, undefined);
}
public set contentRight(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, contentRight) => { properties.contentRight = contentRight; }, newValue);
}
public get languageMode(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.languageMode, undefined);
}
public set languageMode(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, languageMode) => { properties.languageMode = languageMode; }, newValue);
}
public get isAutoResizable(): boolean {
return this.getPropertyOrDefault<azdata.EditorProperties, boolean>((props) => props.isAutoResizable, false);
}
public set isAutoResizable(newValue: boolean) {
this.setPropertyFromUI<azdata.EditorProperties, boolean>((properties, isAutoResizable) => { properties.isAutoResizable = isAutoResizable; }, newValue);
}
public get minimumHeight(): number {
return this.getPropertyOrDefault<azdata.EditorProperties, number>((props) => props.minimumHeight, this._editor.minimumHeight);
}
public set minimumHeight(newValue: number) {
this.setPropertyFromUI<azdata.EditorProperties, number>((properties, minimumHeight) => { properties.minimumHeight = minimumHeight; }, newValue);
}
public get editorUriLeft(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.editorUriLeft, '');
}
public set editorUriLeft(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, editorUriLeft) => { properties.editorUriLeft = editorUriLeft; }, newValue);
}
public get editorUriRight(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.editorUriRight, '');
}
public set editorUriRight(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, editorUriRight) => { properties.editorUriRight = editorUriRight; }, newValue);
}
public get title(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.title, undefined);
}
public set title(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, title) => { properties.title = title; }, newValue);
}
}

View File

@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/divContainer';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy,
} from '@angular/core';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import * as azdata from 'azdata';
import { ContainerBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
class DivItem {
constructor(public descriptor: IComponentDescriptor, public config: azdata.DivItemLayout) { }
}
@Component({
template: `
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" (click)="onClick()" (keyup)="onKey($event)" [tabIndex]="tabIndex">
<div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
`
})
export default class DivContainer extends ContainerBase<azdata.DivItemLayout> implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
@ViewChild('divContainer', { read: ElementRef }) divContainer;
private _height: string;
private _width: string;
private _overflowY: string;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
this._overflowY = ''; // default
}
ngOnInit(): void {
this.baseInit();
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: azdata.DivLayout): void {
this._height = this.convertSize(layout.height);
this._width = this.convertSize(layout.width);
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
if (this.overflowY !== this._overflowY) {
this.updateOverflowY();
}
this.updateScroll();
}
private updateOverflowY() {
this._overflowY = this.overflowY;
if (this._overflowY) {
let element = <HTMLElement>this.divContainer.nativeElement;
element.style.overflowY = this._overflowY;
}
}
private updateScroll() {
let element = <HTMLElement>this.divContainer.nativeElement;
element.scrollTop = element.scrollTop - this.yOffsetChange;
element.dispatchEvent(new Event('scroll'));
}
private onClick() {
this.fireEvent({
eventType: ComponentEventType.onDidClick,
args: undefined
});
}
// CSS-bound properties
public get height(): string {
return this._height;
}
public get width(): string {
return this._width;
}
// CSS-bound properties
public get overflowY(): string {
return this.getPropertyOrDefault<azdata.DivContainerProperties, any>((props) => props.overflowY, '');
}
public set overflowY(newValue: string) {
this.setPropertyFromUI<azdata.DivContainerProperties, any>((properties, newValue) => { properties.overflowY = newValue; }, newValue);
}
public get yOffsetChange(): number {
return this.getPropertyOrDefault<azdata.DivContainerProperties, any>((props) => props.yOffsetChange, 0);
}
public set yOffsetChange(newValue: number) {
this.setPropertyFromUI<azdata.DivContainerProperties, any>((properties, newValue) => { properties.yOffsetChange = newValue; }, newValue);
}
public get clickable(): boolean {
return this.getPropertyOrDefault<azdata.DivContainerProperties, boolean>((props) => props.clickable, false);
}
public get tabIndex(): number {
return this.clickable ? 0 : -1;
}
private onKey(e: KeyboardEvent) {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
this.onClick();
e.stopPropagation();
}
}
private getItemOrder(item: DivItem): number {
return item.config ? item.config.order : 0;
}
private getItemStyles(item: DivItem): { [key: string]: string } {
return item.config && item.config.CSSStyles ? item.config.CSSStyles : {};
}
}

View File

@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dom';
import 'vs/css!./media/highlight';
import 'vs/css!./media/markdown';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy
} from '@angular/core';
import * as azdata from 'azdata';
import * as DOM from 'vs/base/browser/dom';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
@Component({
template: '',
selector: 'modelview-dom-component'
})
export default class DomComponent extends ComponentBase implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _renderedHtml: string;
private _rootElement: HTMLElement;
private _bodyElement: HTMLElement;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
this.createDomElement();
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
this.layout();
}));
}
ngOnDestroy(): void {
this.baseDestroy();
}
private createDomElement() {
this._rootElement = this._el.nativeElement;
this._bodyElement = DOM.$('.dom-body');
this._rootElement.append(this._bodyElement);
}
/// Dom Functions
private setHtml(): void {
if (this.html) {
this._renderedHtml = this.html;
this._bodyElement.innerHTML = this._renderedHtml;
}
}
/// IComponent implementation
public layout(): void {
super.layout();
const element = <HTMLElement>this._el.nativeElement;
element.style.width = this.getWidth();
element.style.height = this.getHeight();
}
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
if (this.html !== this._renderedHtml) {
this.setHtml();
}
}
// CSS-bound properties
public get html(): string {
return this.getPropertyOrDefault<azdata.DomProperties, string>((props) => props.html, '');
}
public set html(newValue: string) {
this.setPropertyFromUI<azdata.DomProperties, string>((properties, html) => { properties.html = html; }, newValue);
}
}

View File

@@ -0,0 +1,207 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { attachEditableDropdownStyler } from 'sql/platform/theme/common/styler';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
@Component({
selector: 'modelview-dropdown',
template: `
<div [style.width]="getWidth()">
<div [style.display]="getEditableDisplay()" #editableDropDown style="width: 100%;"></div>
<div [style.display]="getNotEditableDisplay()" #dropDown style="width: 100%;"></div>
</div>
`
})
export default class DropDownComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _editableDropdown: Dropdown;
private _selectBox: SelectBox;
@ViewChild('editableDropDown', { read: ElementRef }) private _editableDropDownContainer: ElementRef;
@ViewChild('dropDown', { read: ElementRef }) private _dropDownContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(ILayoutService) private readonly layoutService: ILayoutService
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._editableDropDownContainer) {
let dropdownOptions: IDropdownOptions = {
values: [],
strictSelection: false,
placeholder: '',
maxHeight: 125,
ariaLabel: '',
actionLabel: ''
};
this._editableDropdown = new Dropdown(this._editableDropDownContainer.nativeElement, this.contextViewService, this.layoutService,
dropdownOptions);
this._register(this._editableDropdown);
this._register(attachEditableDropdownStyler(this._editableDropdown, this.themeService));
this._register(this._editableDropdown.onValueChange(e => {
if (this.editable) {
this.setSelectedValue(this._editableDropdown.value);
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: e
});
}
}));
}
if (this._dropDownContainer) {
this._selectBox = new SelectBox(this.getValues(), this.getSelectedValue(), this.contextViewService, this._dropDownContainer.nativeElement);
this._selectBox.render(this._dropDownContainer.nativeElement);
this._register(this._selectBox);
this._register(attachSelectBoxStyler(this._selectBox, this.themeService));
this._register(this._selectBox.onDidSelect(e => {
if (!this.editable) {
this.setSelectedValue(this._selectBox.value);
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: e
});
}
}));
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
if (this.editable) {
this._editableDropdown.values = this.getValues();
if (this.value) {
this._editableDropdown.value = this.getSelectedValue();
}
this._editableDropdown.enabled = this.enabled;
this._editableDropdown.fireOnTextChange = this.fireOnTextChange;
} else {
this._selectBox.setOptions(this.getValues());
this._selectBox.selectWithOptionName(this.getSelectedValue());
if (this.enabled) {
this._selectBox.enable();
} else {
this._selectBox.disable();
}
}
}
private getValues(): string[] {
if (this.values && this.values.length > 0) {
if (!this.valuesHaveDisplayName()) {
return this.values as string[];
} else {
return (<azdata.CategoryValue[]>this.values).map(v => v.displayName);
}
}
return [];
}
private valuesHaveDisplayName(): boolean {
return typeof (this.values[0]) !== 'string';
}
private getSelectedValue(): string {
if (this.values && this.values.length > 0 && this.valuesHaveDisplayName()) {
let selectedValue = <azdata.CategoryValue>this.value || <azdata.CategoryValue>this.values[0];
let valueCategory = (<azdata.CategoryValue[]>this.values).find(v => v.name === selectedValue.name);
return valueCategory && valueCategory.displayName;
} else {
if (!this.value && this.values && this.values.length > 0) {
return <string>this.values[0];
}
return <string>this.value;
}
}
private setSelectedValue(newValue: string): void {
if (this.values && this.valuesHaveDisplayName()) {
let valueCategory = (<azdata.CategoryValue[]>this.values).find(v => v.displayName === newValue);
this.value = valueCategory;
} else {
this.value = newValue;
}
}
// CSS-bound properties
private get value(): string | azdata.CategoryValue {
return this.getPropertyOrDefault<azdata.DropDownProperties, string | azdata.CategoryValue>((props) => props.value, '');
}
private get editable(): boolean {
return this.getPropertyOrDefault<azdata.DropDownProperties, boolean>((props) => props.editable, false);
}
private get fireOnTextChange(): boolean {
return this.getPropertyOrDefault<azdata.DropDownProperties, boolean>((props) => props.fireOnTextChange, false);
}
public getEditableDisplay(): string {
return this.editable ? '' : 'none';
}
public getNotEditableDisplay(): string {
return !this.editable ? '' : 'none';
}
private set value(newValue: string | azdata.CategoryValue) {
this.setPropertyFromUI<azdata.DropDownProperties, string | azdata.CategoryValue>(this.setValueProperties, newValue);
}
private get values(): string[] | azdata.CategoryValue[] {
return this.getPropertyOrDefault<azdata.DropDownProperties, string[] | azdata.CategoryValue[]>((props) => props.values, []);
}
private set values(newValue: string[] | azdata.CategoryValue[]) {
this.setPropertyFromUI<azdata.DropDownProperties, string[] | azdata.CategoryValue[]>(this.setValuesProperties, newValue);
}
private setValueProperties(properties: azdata.DropDownProperties, value: string | azdata.CategoryValue): void {
properties.value = value;
}
private setValuesProperties(properties: azdata.DropDownProperties, values: string[] | azdata.CategoryValue[]): void {
properties.values = values;
}
}

View File

@@ -0,0 +1,201 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/editor';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy
} from '@angular/core';
import * as azdata from 'azdata';
import * as DOM from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextModel } from 'vs/editor/common/model';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { QueryTextEditor } from 'sql/workbench/electron-browser/modelComponents/queryTextEditor';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SimpleProgressService } from 'vs/editor/standalone/browser/simpleServices';
import { IProgressService } from 'vs/platform/progress/common/progress';
@Component({
template: '',
selector: 'modelview-editor-component'
})
export default class EditorComponent extends ComponentBase implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _editor: QueryTextEditor;
private _editorInput: UntitledEditorInput;
private _editorModel: ITextModel;
private _renderedContent: string;
private _languageMode: string;
private _uri: string;
private _isAutoResizable: boolean;
private _minimumHeight: number;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IModelService) private _modelService: IModelService,
@Inject(IModeService) private _modeService: IModeService
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
this._createEditor();
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
this.layout();
}));
}
private _createEditor(): void {
let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()]));
this._editor = instantiationService.createInstance(QueryTextEditor);
this._editor.create(this._el.nativeElement);
this._editor.setVisible(true);
let uri = this.createUri();
this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, 'plaintext', '', '');
this._editor.setInput(this._editorInput, undefined);
this._editorInput.resolve().then(model => {
this._editorModel = model.textEditorModel;
this.fireEvent({
eventType: ComponentEventType.onComponentCreated,
args: this._uri
});
});
this._register(this._editor);
this._register(this._editorInput);
this._register(this._editorModel.onDidChangeContent(e => {
this.content = this._editorModel.getValue();
if (this._isAutoResizable) {
if (this._minimumHeight) {
this._editor.setMinimumHeight(this._minimumHeight);
}
this._editor.setHeightToScrollHeight();
}
// Notify via an event so that extensions can detect and propagate changes
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: e
});
}));
}
private createUri(): URI {
let uri = URI.from({ scheme: Schemas.untitled, path: `${this.descriptor.type}-${this.descriptor.id}` });
// Use this to set the internal (immutable) and public (shared with extension) uri properties
this._uri = uri.toString();
this.editorUri = this._uri;
return uri;
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public layout(): void {
let width: number = this.convertSizeToNumber(this.width);
let height: number = this.convertSizeToNumber(this.height);
if (this._isAutoResizable) {
this._editor.setHeightToScrollHeight();
height = Math.max(this._editor.scrollHeight, this._minimumHeight ? this._minimumHeight : 0);
}
this._editor.layout(new DOM.Dimension(
width && width > 0 ? width : DOM.getContentWidth(this._el.nativeElement),
height && height > 0 ? height : DOM.getContentHeight(this._el.nativeElement)));
let element = <HTMLElement>this._el.nativeElement;
element.style.position = this.position;
}
/// Editor Functions
private updateModel() {
if (this._editorModel) {
this._renderedContent = this.content;
this._modelService.updateModel(this._editorModel, this._renderedContent);
}
}
private updateLanguageMode() {
if (this._editorModel && this._editor) {
this._languageMode = this.languageMode;
let languageSelection = this._modeService.create(this._languageMode);
this._modelService.setMode(this._editorModel, languageSelection);
}
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
if (this.content !== this._renderedContent) {
this.updateModel();
}
if (this.languageMode !== this._languageMode) {
this.updateLanguageMode();
}
// Intentionally always updating editorUri as it's wiped out by parent setProperties call.
this.editorUri = this._uri;
this._isAutoResizable = this.isAutoResizable;
this._minimumHeight = this.minimumHeight;
}
// CSS-bound properties
public get content(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.content, undefined);
}
public set content(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, content) => { properties.content = content; }, newValue);
}
public get languageMode(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.languageMode, undefined);
}
public set languageMode(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, languageMode) => { properties.languageMode = languageMode; }, newValue);
}
public get isAutoResizable(): boolean {
return this.getPropertyOrDefault<azdata.EditorProperties, boolean>((props) => props.isAutoResizable, false);
}
public set isAutoResizable(newValue: boolean) {
this.setPropertyFromUI<azdata.EditorProperties, boolean>((properties, isAutoResizable) => { properties.isAutoResizable = isAutoResizable; }, newValue);
}
public get minimumHeight(): number {
return this.getPropertyOrDefault<azdata.EditorProperties, number>((props) => props.minimumHeight, this._editor.minimumHeight);
}
public set minimumHeight(newValue: number) {
this.setPropertyFromUI<azdata.EditorProperties, number>((properties, minimumHeight) => { properties.minimumHeight = minimumHeight; }, newValue);
}
public get editorUri(): string {
return this.getPropertyOrDefault<azdata.EditorProperties, string>((props) => props.editorUri, '');
}
public set editorUri(newValue: string) {
this.setPropertyFromUI<azdata.EditorProperties, string>((properties, editorUri) => { properties.editorUri = editorUri; }, newValue);
}
}

View File

@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileBrowserViewModel } from 'sql/workbench/services/fileBrowser/common/fileBrowserViewModel';
import { FileNode } from 'sql/workbench/services/fileBrowser/common/fileNode';
import { FileBrowserTreeView } from 'sql/workbench/services/fileBrowser/browser/fileBrowserTreeView';
@Component({
selector: 'modelview-fileBrowserTree',
template: `
<div #fileBrowserTree [style.width]="getWidth()" [style.height]="getHeight()"></div>
`
})
export default class FileBrowserTreeComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _treeView: FileBrowserTreeView;
private _viewModel: FileBrowserViewModel;
private _fileFilters: [{ label: string, filters: string[] }] = [
{ label: 'All Files', filters: ['*'] }
];
@ViewChild('fileBrowserTree', { read: ElementRef }) private _treeContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
this._viewModel = this._instantiationService.createInstance(FileBrowserViewModel);
this._viewModel.onAddFileTree(args => this.handleOnAddFileTree(args.rootNode, args.selectedNode, args.expandedNodes));
this._viewModel.onPathValidate(args => this.handleOnValidate(args.succeeded, args.message));
}
public initialize() {
this._viewModel.initialize(this.ownerUri, '', this._fileFilters, 'Backup');
this._treeView = this._instantiationService.createInstance(FileBrowserTreeView);
this._treeView.setOnClickedCallback((arg) => {
this.onClicked(arg);
});
this._treeView.setOnDoubleClickedCallback((arg) => this.onDoubleClicked(arg));
this._register(this._treeView);
this._viewModel.openFileBrowser(0, false);
}
private onClicked(selectedNode: FileNode) {
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: { fullPath: selectedNode.fullPath, isFile: selectedNode.isFile }
});
}
private onDoubleClicked(selectedNode: FileNode) {
if (selectedNode.isFile === true) {
}
}
private handleOnAddFileTree(rootNode: FileNode, selectedNode: FileNode, expandedNodes: FileNode[]) {
this.updateFileTree(rootNode, selectedNode, expandedNodes);
}
private updateFileTree(rootNode: FileNode, selectedNode: FileNode, expandedNodes: FileNode[]): void {
this._treeView.renderBody(this._treeContainer.nativeElement, rootNode, selectedNode, expandedNodes);
this._treeView.setVisible(true);
this.layoutTree();
this._changeRef.detectChanges();
}
private handleOnValidate(succeeded: boolean, errorMessage: string) {
if (succeeded === false) {
if (errorMessage === '') {
errorMessage = 'The provided path is invalid.';
}
}
}
public validate(): Thenable<boolean> {
return super.validate().then(valid => {
// TODO: tree validation?
return valid;
});
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(): void {
// TODO allow configuring the look and feel
this.layout();
}
private layoutTree(): void {
this._treeView.layout(700);
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this.validate();
if (this.ownerUri) {
this.initialize();
}
}
// CSS-bound properties
public get ownerUri(): string {
return this.getPropertyOrDefault<azdata.FileBrowserTreeProperties, string>((props) => props.ownerUri, '');
}
public set ownerUri(newValue: string) {
this.setPropertyFromUI<azdata.FileBrowserTreeProperties, string>((props, value) => props.ownerUri = value, newValue);
}
}

View File

@@ -0,0 +1,119 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/flexContainer';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ElementRef, OnDestroy
} from '@angular/core';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { FlexLayout, FlexItemLayout } from 'azdata';
import { ContainerBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
export class FlexItem {
constructor(public descriptor: IComponentDescriptor, public config: FlexItemLayout) { }
}
@Component({
template: `
<div *ngIf="items" class="flexContainer" [style.flexFlow]="flexFlow" [style.justifyContent]="justifyContent" [style.position]="position"
[style.alignItems]="alignItems" [style.alignContent]="alignContent" [style.height]="height" [style.width]="width">
<div *ngFor="let item of items" [style.flex]="getItemFlex(item)" [style.textAlign]="textAlign" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
`
})
export default class FlexContainer extends ContainerBase<FlexItemLayout> implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _flexFlow: string;
private _justifyContent: string;
private _alignItems: string;
private _alignContent: string;
private _textAlign: string;
private _height: string;
private _width: string;
private _position: string;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
this._flexFlow = ''; // default
this._justifyContent = ''; // default
}
ngOnInit(): void {
this.baseInit();
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: FlexLayout): void {
this._flexFlow = layout.flexFlow ? layout.flexFlow : '';
this._justifyContent = layout.justifyContent ? layout.justifyContent : '';
this._alignItems = layout.alignItems ? layout.alignItems : '';
this._alignContent = layout.alignContent ? layout.alignContent : '';
this._textAlign = layout.textAlign ? layout.textAlign : '';
this._position = layout.position ? layout.position : '';
this._height = this.convertSize(layout.height);
this._width = this.convertSize(layout.width);
this.layout();
}
// CSS-bound properties
public get flexFlow(): string {
return this._flexFlow;
}
public get justifyContent(): string {
return this._justifyContent;
}
public get alignItems(): string {
return this._alignItems;
}
public get height(): string {
return this._height;
}
public get width(): string {
return this._width;
}
public get alignContent(): string {
return this._alignContent;
}
public get textAlign(): string {
return this._textAlign;
}
public get position(): string {
return this._position;
}
private getItemFlex(item: FlexItem): string {
return item.config ? item.config.flex : '1 1 auto';
}
private getItemOrder(item: FlexItem): number {
return item.config ? item.config.order : 0;
}
private getItemStyles(item: FlexItem): { [key: string]: string } {
return item.config && item.config.CSSStyles ? item.config.CSSStyles : {};
}
}

View File

@@ -0,0 +1,231 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/formLayout';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { FormLayout, FormItemLayout } from 'azdata';
import { ContainerBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
export interface TitledFormItemLayout {
title: string;
actions?: string[];
isFormComponent: boolean;
horizontal: boolean;
componentWidth?: number | string;
componentHeight?: number | string;
titleFontSize?: number | string;
required?: boolean;
info?: string;
isInGroup?: boolean;
isGroupLabel?: boolean;
}
export interface FormLayout {
width: number;
}
class FormItem {
constructor(public descriptor: IComponentDescriptor, public config: TitledFormItemLayout) { }
}
@Component({
template: `
<div #container *ngIf="items" class="form-table" [style.padding]="getFormPadding()" [style.width]="getFormWidth()" [style.height]="getFormHeight()">
<ng-container *ngFor="let item of items">
<div class="form-row" *ngIf="isGroupLabel(item)" [style.font-size]="getItemTitleFontSize(item)">
<div class="form-item-row form-group-label">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
<div class="form-row" *ngIf="isFormComponent(item)" [style.height]="getRowHeight(item)">
<ng-container *ngIf="isHorizontal(item)">
<div class="form-cell" [style.font-size]="getItemTitleFontSize(item)" [ngClass]="{'form-group-item': isInGroup(item)}">
{{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span>
<span class="icon help form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span>
</div>
<div class="form-cell">
<div class="form-component-container">
<div [style.width]="getComponentWidth(item)" [ngClass]="{'form-input-flex': !getComponentWidth(item)}">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
<div *ngIf="itemHasActions(item)" class="form-component-actions">
<ng-container *ngFor="let actionItem of getActionComponents(item)">
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore" >
</model-component-wrapper>
</ng-container>
</div>
</div>
</div>
</ng-container>
<div class="form-vertical-container" *ngIf="isVertical(item)" [style.height]="getRowHeight(item)" [ngClass]="{'form-group-item': isInGroup(item)}">
<div class="form-item-row" [style.font-size]="getItemTitleFontSize(item)">
{{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span>
<span class="icon help form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span>
</div>
<div class="form-item-row" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)">
</model-component-wrapper>
</div>
<div *ngIf="itemHasActions(item)" class="form-item-row form-actions-table form-item-last-row">
<div *ngFor="let actionItem of getActionComponents(item)" class="form-actions-cell" >
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
</div>
</div>
</ng-container>
</div>
`
})
export default class FormContainer extends ContainerBase<FormItemLayout> implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _alignItems: string;
private _alignContent: string;
private _formLayout: FormLayout;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngOnDestroy(): void {
this.baseDestroy();
}
ngAfterViewInit(): void {
}
public layout(): void {
super.layout();
}
/// IComponent implementation
public get alignItems(): string {
return this._alignItems;
}
public get alignContent(): string {
return this._alignContent;
}
private getFormWidth(): string {
return this.convertSize(this._formLayout && this._formLayout.width, '');
}
private getFormPadding(): string {
return this._formLayout && this._formLayout.padding ? this._formLayout.padding : '10px 30px 0px 30px';
}
private getFormHeight(): string {
return this.convertSize(this._formLayout && this._formLayout.height, '');
}
private getComponentWidth(item: FormItem): string {
let itemConfig = item.config;
return (itemConfig && itemConfig.componentWidth) ? this.convertSize(itemConfig.componentWidth, '') : '';
}
private getRowHeight(item: FormItem): string {
let itemConfig = item.config;
return (itemConfig && itemConfig.componentHeight) ? this.convertSize(itemConfig.componentHeight, '') : '';
}
private isItemRequired(item: FormItem): boolean {
let itemConfig = item.config;
return itemConfig && itemConfig.required;
}
private getItemInfo(item: FormItem): string {
let itemConfig = item.config;
return itemConfig && itemConfig.info;
}
private itemHasInfo(item: FormItem): boolean {
let itemConfig = item.config;
return itemConfig && itemConfig.info !== undefined;
}
private getItemTitle(item: FormItem): string {
let itemConfig = item.config;
return itemConfig ? itemConfig.title : '';
}
private getItemTitleFontSize(item: FormItem): string {
let defaultFontSize = '14px';
if (this.isInGroup(item)) {
defaultFontSize = '12px';
}
let itemConfig = item.config;
return itemConfig && itemConfig.titleFontSize ? this.convertSize(itemConfig.titleFontSize, defaultFontSize) : defaultFontSize;
}
private getActionComponents(item: FormItem): FormItem[] {
let items = this.items;
let itemConfig = item.config;
if (itemConfig && itemConfig.actions) {
let resultItems = itemConfig.actions.map(x => {
let actionComponent = items.find(i => i.descriptor.id === x);
return <FormItem>actionComponent;
});
return resultItems.filter(r => r && r.descriptor);
}
return [];
}
private isGroupLabel(item: FormItem): boolean {
return item && item.config && item.config.isGroupLabel;
}
private isInGroup(item: FormItem): boolean {
return item && item.config && item.config.isInGroup;
}
private isFormComponent(item: FormItem): boolean {
return item && item.config && item.config.isFormComponent;
}
private itemHasActions(item: FormItem): boolean {
let itemConfig = item.config;
return itemConfig && itemConfig.actions !== undefined && itemConfig.actions.length > 0;
}
public setLayout(layout: FormLayout): void {
this._formLayout = layout;
this.layout();
}
private isHorizontal(item: FormItem): boolean {
return item && item.config && item.config.horizontal;
}
private isVertical(item: FormItem): boolean {
return item && item.config && !item.config.horizontal;
}
}

View File

@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/groupLayout';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { GroupLayout } from 'azdata';
import { ContainerBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
@Component({
selector: 'modelview-groupContainer',
template: `
<div *ngIf="hasHeader()" [class]="getHeaderClass()" (click)="changeState()">
{{_containerLayout.header}}
</div>
<div #container *ngIf="items" class="modelview-group-container" [style.width]="getContainerWidth()" [style.display]="getContainerDisplayStyle()">
<ng-container *ngFor="let item of items">
<div class="modelview-group-row" >
<div class="modelview-group-cell">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore" >
</model-component-wrapper>
</div>
</div>
</ng-container>
</div>
`
})
export default class GroupContainer extends ContainerBase<GroupLayout> implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _containerLayout: GroupLayout;
private _collapsed: boolean;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
this._collapsed = false;
}
ngOnInit(): void {
this.baseInit();
}
ngOnDestroy(): void {
this.baseDestroy();
}
ngAfterViewInit(): void {
}
/// IComponent implementation
public setLayout(layout: GroupLayout): void {
this._containerLayout = layout;
this._collapsed = !!layout.collapsed;
this.layout();
}
private hasHeader(): boolean {
return this._containerLayout && this._containerLayout && this._containerLayout.header !== undefined;
}
private isCollapsible(): boolean {
return this.hasHeader() && this._containerLayout.collapsible === true;
}
private getContainerWidth(): string {
if (this._containerLayout && this._containerLayout.width) {
let width: string = this._containerLayout.width.toString();
if (!width.endsWith('%') && !width.toLowerCase().endsWith('px')) {
width = width + 'px';
}
return width;
} else {
return '100%';
}
}
private getContainerDisplayStyle(): string {
return !this.isCollapsible() || !this._collapsed ? 'block' : 'none';
}
private getHeaderClass(): string {
if (this.isCollapsible()) {
let modifier = this._collapsed ? 'collapsed' : 'expanded';
return `modelview-group-header-collapsible ${modifier}`;
} else {
return 'modelview-group-header';
}
}
private changeState(): void {
if (this.isCollapsible()) {
this._collapsed = !this._collapsed;
this._changeRef.detectChanges();
}
}
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
OnDestroy, AfterViewInit, ElementRef
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
@Component({
selector: 'modelview-hyperlink',
template: `<a [href]="getUrl()" target="blank">{{getLabel()}}</a>`
})
export default class HyperlinkComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
}
ngOnDestroy(): void {
this.baseDestroy();
}
public setLayout(layout: any): void {
this.layout();
}
public set label(newValue: string) {
this.setPropertyFromUI<azdata.HyperlinkComponentProperties, string>((properties, value) => { properties.label = value; }, newValue);
}
public get label(): string {
return this.getPropertyOrDefault<azdata.HyperlinkComponentProperties, string>((props) => props.label, '');
}
public getLabel(): string {
return this.label;
}
public set url(newValue: string) {
this.setPropertyFromUI<azdata.HyperlinkComponentProperties, string>((properties, value) => { properties.url = value; }, newValue);
}
public get url(): string {
return this.getPropertyOrDefault<azdata.HyperlinkComponentProperties, string>((props) => props.url, '');
}
public getUrl(): string {
return this.url;
}
}

View File

@@ -0,0 +1,292 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { attachInputBoxStyler } from 'sql/platform/theme/common/styler';
import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import * as nls from 'vs/nls';
import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
import * as DomUtils from 'vs/base/browser/dom';
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
@Component({
selector: 'modelview-inputBox',
template: `
<div [style.display]="getInputBoxDisplay()" #input style="width: 100%"></div>
<div [style.display]="getTextAreaDisplay()" #textarea style="width: 100%"></div>
`
})
export default class InputBoxComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _input: InputBox;
private _textAreaInput: InputBox;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
@ViewChild('textarea', { read: ElementRef }) private _textareaContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
let inputOptions: IInputOptions = {
placeholder: '',
ariaLabel: '',
validationOptions: {
validation: () => {
if (this.valid) {
return undefined;
} else {
return {
content: this.inputElement.inputElement.validationMessage || nls.localize('invalidValueError', 'Invalid value'),
type: MessageType.ERROR
};
}
}
},
useDefaultValidation: true
};
if (this._inputContainer) {
this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions);
this.registerInput(this._input, () => !this.multiline);
}
if (this._textareaContainer) {
let textAreaInputOptions = Object.assign({}, inputOptions, { flexibleHeight: true, type: 'textarea' });
this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions);
this.onkeydown(this._textAreaInput.inputElement, (e: StandardKeyboardEvent) => {
if (this.tryHandleKeyEvent(e)) {
e.stopPropagation();
}
// Else assume that keybinding service handles routing this to a command
});
this.registerInput(this._textAreaInput, () => this.multiline);
}
this.inputElement.hideErrors = true;
}
private onkeydown(domNode: HTMLElement, listener: (e: IKeyboardEvent) => void): void {
this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.KEY_DOWN, (e: KeyboardEvent) => listener(new StandardKeyboardEvent(e))));
}
private tryHandleKeyEvent(e: StandardKeyboardEvent): boolean {
let handled: boolean = false;
if (this.multiline && e.keyCode === KeyCode.Enter) {
handled = true;
}
return handled;
}
private get inputElement(): InputBox {
return this.multiline ? this._textAreaInput : this._input;
}
private registerInput(input: InputBox, checkOption: () => boolean): void {
if (input) {
this._validations.push(() => !input.inputElement.validationMessage);
this._register(input);
this._register(attachInputBoxStyler(input, this.themeService, {
inputValidationInfoBackground: inputBackground,
inputValidationInfoBorder: inputBorder,
}));
this._register(input.onDidChange(async e => {
if (checkOption()) {
this.value = input.value;
await this.validate();
if (input.hideErrors) {
input.hideErrors = false;
}
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: e
});
}
}));
}
}
public getInputBoxDisplay(): string {
return !this.multiline ? '' : 'none';
}
public getTextAreaDisplay(): string {
return this.multiline ? '' : 'none';
}
public validate(): Thenable<boolean> {
return super.validate().then(valid => {
this.inputElement.validate();
return valid;
});
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public layout(): void {
super.layout();
this.layoutInputBox();
}
private layoutInputBox(): void {
if (this.width) {
this.inputElement.width = this.convertSizeToNumber(this.width);
}
if (this.height) {
this.inputElement.setHeight(this.convertSize(this.height));
}
}
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this.setInputProperties(this.inputElement);
this.validate();
}
private setInputProperties(input: InputBox): void {
if (!this.multiline) {
input.inputElement.type = this.inputType;
if (this.inputType === 'number') {
input.inputElement.step = 'any';
if (this.min) {
input.inputElement.min = this.min.toString();
}
if (this.max) {
input.inputElement.max = this.max.toString();
}
}
}
input.value = this.value;
input.setAriaLabel(this.ariaLabel);
input.setPlaceHolder(this.placeHolder);
input.setEnabled(this.enabled);
this.layoutInputBox();
if (this.multiline) {
if (this.rows) {
this.inputElement.rows = this.rows;
}
if (this.columns) {
this.inputElement.columns = this.columns;
}
}
input.inputElement.required = this.required;
}
// CSS-bound properties
public get value(): string {
return this.getPropertyOrDefault<azdata.InputBoxProperties, string>((props) => props.value, '');
}
public set value(newValue: string) {
this.setPropertyFromUI<azdata.InputBoxProperties, string>((props, value) => props.value = value, newValue);
}
public get ariaLabel(): string {
return this.getPropertyOrDefault<azdata.InputBoxProperties, string>((props) => props.ariaLabel, '');
}
public set ariaLabel(newValue: string) {
this.setPropertyFromUI<azdata.InputBoxProperties, string>((props, value) => props.ariaLabel = value, newValue);
}
public get placeHolder(): string {
return this.getPropertyOrDefault<azdata.InputBoxProperties, string>((props) => props.placeHolder, '');
}
public set placeHolder(newValue: string) {
this.setPropertyFromUI<azdata.InputBoxProperties, string>((props, value) => props.placeHolder = value, newValue);
}
public set columns(newValue: number) {
this.setPropertyFromUI<azdata.InputBoxProperties, number>((props, value) => props.columns = value, newValue);
}
public get rows(): number {
return this.getPropertyOrDefault<azdata.InputBoxProperties, number>((props) => props.rows, undefined);
}
public get columns(): number {
return this.getPropertyOrDefault<azdata.InputBoxProperties, number>((props) => props.columns, undefined);
}
public set rows(newValue: number) {
this.setPropertyFromUI<azdata.InputBoxProperties, number>((props, value) => props.rows = value, newValue);
}
public get min(): number {
return this.getPropertyOrDefault<azdata.InputBoxProperties, number>((props) => props.min, undefined);
}
public set min(newValue: number) {
this.setPropertyFromUI<azdata.InputBoxProperties, number>((props, value) => props.min = value, newValue);
}
public get max(): number {
return this.getPropertyOrDefault<azdata.InputBoxProperties, number>((props) => props.max, undefined);
}
public set max(newValue: number) {
this.setPropertyFromUI<azdata.InputBoxProperties, number>((props, value) => props.max = value, newValue);
}
public get inputType(): string {
return this.getPropertyOrDefault<azdata.InputBoxProperties, string>((props) => props.inputType, 'text');
}
public set inputType(newValue: string) {
this.setPropertyFromUI<azdata.InputBoxProperties, string>((props, value) => props.inputType = value, newValue);
}
public get multiline(): boolean {
return this.getPropertyOrDefault<azdata.InputBoxProperties, boolean>((props) => props.multiline, false);
}
public set multiline(newValue: boolean) {
this.setPropertyFromUI<azdata.InputBoxProperties, boolean>((props, value) => props.multiline = value, newValue);
}
public get required(): boolean {
return this.getPropertyOrDefault<azdata.InputBoxProperties, boolean>((props) => props.required, false);
}
public set required(newValue: boolean) {
this.setPropertyFromUI<azdata.InputBoxProperties, boolean>((props, value) => props.required = value, newValue);
}
}

View File

@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { InjectionToken } from '@angular/core';
import { IDisposable } from 'vs/base/common/lifecycle';
/**
* An instance of a model-backed component. This will be a UI element
*
* @export
*/
export interface IComponent extends IDisposable {
descriptor: IComponentDescriptor;
modelStore: IModelStore;
layout();
registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable;
clearContainer?: () => void;
addToContainer?: (componentDescriptor: IComponentDescriptor, config: any, index?: number) => void;
removeFromContainer?: (componentDescriptor: IComponentDescriptor) => void;
setLayout?: (layout: any) => void;
getHtml: () => any;
setProperties?: (properties: { [key: string]: any; }) => void;
enabled: boolean;
readonly valid?: boolean;
validate(): Thenable<boolean>;
setDataProvider(handle: number, componentId: string, context: any): void;
refreshDataProvider(item: any): void;
}
export const COMPONENT_CONFIG = new InjectionToken<IComponentConfig>('component_config');
export interface IComponentConfig {
descriptor: IComponentDescriptor;
modelStore: IModelStore;
}
/**
* Defines a component and can be used to map from the model-backed version of the
* world to the frontend UI;
*
* @export
*/
export interface IComponentDescriptor {
/**
* The type of this component. Used to map to the correct angular selector
* when loading the component
*/
type: string;
/**
* A unique ID for this component
*/
id: string;
}
export interface IComponentEventArgs {
eventType: ComponentEventType;
args: any;
componentId?: string;
}
export enum ComponentEventType {
PropertiesChanged,
onDidChange,
onDidClick,
validityChanged,
onMessage,
onSelectedRowChanged,
onComponentCreated
}
export interface IModelStore {
/**
* Creates and saves the reference of a component descriptor.
* This can be used during creation of a component later
*/
createComponentDescriptor(type: string, createComponentDescriptor): IComponentDescriptor;
/**
* gets the descriptor for a previously created component ID
*/
getComponentDescriptor(componentId: string): IComponentDescriptor;
registerComponent(component: IComponent): void;
unregisterComponent(component: IComponent): void;
getComponent(componentId: string): IComponent;
/**
* Runs on a component immediately if the component exists, or runs on
* registration of the component otherwise
*
* @param componentId unique identifier of the component
* @param action some action to perform
*/
eventuallyRunOnComponent<T>(componentId: string, action: (component: IComponent) => T): Promise<T>;
/**
* Register a callback that will validate components when given a component ID
*/
registerValidationCallback(callback: (componentId: string) => Thenable<boolean>): void;
/**
* Run all validations for the given component and return the new validation value
*/
validate(component: IComponent): Thenable<boolean>;
}

View File

@@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { ListBox } from 'sql/base/browser/ui/listBox/listBox';
import { attachListBoxStyler } from 'sql/platform/theme/common/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@Component({
selector: 'modelview-listBox',
template: `
<div #input style="width: 100%"></div>
`
})
export default class ListBoxComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _input: ListBox;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(IClipboardService) private clipboardService: IClipboardService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
this._input = new ListBox([], this.contextViewService, this.clipboardService);
this._input.render(this._inputContainer.nativeElement);
this._register(this._input);
this._register(attachListBoxStyler(this._input, this.themeService));
this._register(this._input.onDidSelect(e => {
this.selectedRow = e.index;
this.fireEvent({
eventType: ComponentEventType.onSelectedRowChanged,
args: e
});
}));
}
}
public validate(): Thenable<boolean> {
return super.validate().then(valid => {
return valid;
});
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._input.setOptions(this.values.map(value => { return { text: value }; }), this.selectedRow);
this.validate();
}
// CSS-bound properties
private get values(): string[] {
return this.getPropertyOrDefault<azdata.ListBoxProperties, string[]>((props) => props.values, undefined);
}
private set values(newValue: string[]) {
this.setPropertyFromUI<azdata.ListBoxProperties, string[]>((props, value) => props.values = value, newValue);
}
private get selectedRow(): number {
return this.getPropertyOrDefault<azdata.ListBoxProperties, number>((props) => props.selectedRow, undefined);
}
private set selectedRow(newValue: number) {
this.setPropertyFromUI<azdata.ListBoxProperties, number>((props, value) => props.selectedRow = value, newValue);
}
}

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/loadingComponent';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, OnDestroy, AfterViewInit, ElementRef
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import * as nls from 'vs/nls';
@Component({
selector: 'modelview-loadingComponent',
template: `
<div class="modelview-loadingComponent-container" *ngIf="loading">
<div class="modelview-loadingComponent-spinner" *ngIf="loading" [title]=_loadingTitle #spinnerElement></div>
</div>
<model-component-wrapper #childElement [descriptor]="_component" [modelStore]="modelStore" *ngIf="_component" [ngClass]="{'modelview-loadingComponent-content-loading': loading}">
</model-component-wrapper>
`
})
export default class LoadingComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
private readonly _loadingTitle = nls.localize('loadingMessage', 'Loading');
private _component: IComponentDescriptor;
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
this._validations.push(() => {
if (!this._component) {
return true;
}
return this.modelStore.getComponent(this._component.id).validate();
});
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
this.setLayout();
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(): void {
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
}
public get loading(): boolean {
return this.getPropertyOrDefault<azdata.LoadingComponentProperties, boolean>((props) => props.loading, false);
}
public set loading(newValue: boolean) {
this.setPropertyFromUI<azdata.LoadingComponentProperties, boolean>((properties, value) => { properties.loading = value; }, newValue);
this.layout();
}
public addToContainer(componentDescriptor: IComponentDescriptor): void {
this._component = componentDescriptor;
this.layout();
}
}

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/loadingComponent';
import { Component, Input } from '@angular/core';
import * as nls from 'vs/nls';
@Component({
selector: 'loading-spinner',
template: `
<div class="modelview-loadingComponent-container" *ngIf="loading">
<div class="modelview-loadingComponent-spinner" *ngIf="loading" [title]=_loadingTitle #spinnerElement></div>
</div>
`
})
export default class LoadingSpinner {
private readonly _loadingTitle = nls.localize('loadingMessage', 'Loading');
@Input() loading: boolean;
}

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
modelview-button a.monaco-button.monaco-text-button.icon {
background-repeat: no-repeat;
background-position: 0% 50%;
background-size: contain;
}

View File

@@ -0,0 +1,200 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.model-card {
position: relative;
display: inline-block;
height: 90%;
width: auto;
margin: 15px;
border-width: 1px;
border-style: solid;
text-align: left;
vertical-align: top;
}
.model-card-list-item.selected,
.model-card.selected {
border-color: rgb(0, 120, 215);
box-shadow: rgba(0, 120, 215, 0.75) 0px 0px 6px;
}
.model-card-list-item.unselected,
.model-card.unselected {
border-color: rgb(214, 214, 214);
box-shadow: none;
}
.model-card .card-content {
position: relative;
display: inline-block;
height: auto;
width: auto;
padding: 10px 45px 20px 45px;
min-height: 30px;
min-width: 30px;
}
.model-card .card-vertical-button {
position: relative;
display: flex;
flex-direction: column;
text-align: center;
height: auto;
width: auto;
padding: 5px 5px 5px 5px;
min-height: 130px;
min-width: 130px;
}
.model-card .card-label {
font-size: 12px;
font-weight: bold;
}
.model-card .card-value {
font-size: 12px;
line-height: 18px;
}
.model-card .iconContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-grow: 1;
border-bottom-width: 1px;
border-bottom-style: solid;
padding: 10px 0px 10px 0px;
border-color: rgb(214, 214, 214);
}
.model-card .cardIcon {
display: inline-block;
flex-grow: 1;
width: 100%;
height: 100%;
max-width: 50px;
max-height: 50px;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
.model-card .card-status {
position: absolute;
top: 7px;
left: 5px;
overflow: hidden;
width: 22px;
height: 22px;
}
.model-card .status-content {
position: absolute;
top: 0px;
right: 0px;
min-width: 16px;
height: 16px;
border-radius: 8px;
text-align: center;
}
.model-card-list-item .selection-indicator-container,
.model-card .selection-indicator-container {
position: absolute;
top: 5px;
right: 5px;
overflow: hidden;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: white;
border-width: 1px;
border-color: rgb(0, 120, 215);
border-style: solid;
}
.model-card-list-item .selection-indicator-container,
.model-card .selection-indicator-container {
position: absolute;
overflow: hidden;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: white;
border-width: 1px;
border-color: rgb(0, 120, 215);
border-style: solid;
}
.model-card-list-item .selection-indicator-container {
top: 10px;
right: 10px;
}
.model-card .selection-indicator-container {
top: 5px;
right: 5px;
}
.model-card-list-item .selection-indicator,
.model-card .selection-indicator {
margin: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgb(0, 120, 215);
}
.model-card .model-table {
border-spacing: 5px;
}
.model-table .table-row {
width: auto;
clear: both;
}
.model-table .table-cell {
vertical-align: top;
padding: 7px;
}
.model-table a {
cursor: pointer;
text-decoration: underline
}
.model-card-list-item {
display: inline-block;
height: 100%;
width: 100%;
margin: 5px 0px 5px 0px;
border-width: 1px;
border-style: solid;
text-align: left;
vertical-align: top;
}
.model-card-list-item .list-item-content {
height: auto;
padding: 5px 26px 5px 5px;
min-height: 30px;
min-width: 300px;
}
.model-card-list-item .list-item-icon {
background-position: 2px 2px;
padding-left:22px;
font-size: 15px;
background-repeat: no-repeat;
background-size: 16px 16px;
}
.model-card-list-item .list-item-description {
padding-left:22px;
}

View File

@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.declarative-table {
padding: 5px 30px 0px 30px;
box-sizing: border-box;
border-collapse: collapse;
}
.declarative-table-header {
padding: 5px;
border: 1px solid gray;
background-color: #F5F5F5;
vertical-align: top;
}
.vs-dark .declarative-table-header {
padding: 5px;
border: 1px solid gray;
background-color: #333334;
}
.hc-black .declarative-table-header {
padding: 5px;
border: 1px solid gray;
background-color: #333334;
}
.declarative-table-cell {
padding: 5px;
border: 1px solid gray;
}
.declarative-table [role="gridcell"]:focus,
.declarative-table [role="gridcell"] *:focus,
.declarative-table [role="grid"] [tabindex="0"]:focus {
outline: #005a9c;
outline-style: dotted;
outline-width: 3px;
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.divContainer {
display: block;
height: 100%;
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
modelview-dom-component {
display: block;
-webkit-user-select: text;
}

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
modelview-editor-component {
height: 100%;
width : 100%;
display: block;
}
modelview-diff-editor-component {
height: 100%;
width : 100%;
display: block;
}
.vs-dark modelview-diff-editor-title {
background: #444444;
}
modelview-diff-editor-title {
background: #f4f4f4;
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.flexContainer {
display: flex;
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.form-table {
display: table;
padding: 5px 30px 0px 30px;
box-sizing: border-box;
}
.form-actions-table {
display: table;
}
.form-row {
display: table-row;
}
.form-item-row {
padding-bottom: 5px;
}
.form-vertical-container {
padding-bottom: 5px;
width: 100%;
}
.form-cell {
padding-bottom: 10px;
padding-right: 5px;
display: table-cell;
}
.form-component-container {
display: flex;
flex-direction: row;
}
.form-input-flex {
flex: 1;
}
.form-required {
color: red;
padding-left: 5px;
}
.form-info {
width: 15px;
height: 15px;
}
.form-component-actions {
padding-left: 5px;
}
.form-actions-cell {
padding-top: 5px;
padding-right: 5px;
display: table-cell;
}
.form-group-label {
padding-top: 3px;
padding-bottom: 0px;
font-weight: bold;
}

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.modelview-group-container {
display: table;
padding: 5px 10px 5px 10px;
box-sizing: border-box;
}
.modelview-group-row {
display: table-row;
}
.modelview-group-header-collapsible,
.modelview-group-header {
padding-bottom: 5px;
font-size: 14px;
}
.modelview-group-header-collapsible {
padding-left: 20px;
background-position: 2px 2px;
background-size: 16px 16px;
background-repeat: no-repeat;
cursor: pointer;
}
.vs .modelview-group-header-collapsible.expanded {
background-image: url("../../media/icons/expanded.svg");
}
.vs-dark .modelview-group-header-collapsible.expanded,
.hc-black .modelview-group-header-collapsible.expanded {
background-image: url("../../media/icons/expanded_inverse.svg");
}
.vs .modelview-group-header-collapsible.collapsed {
background-image: url("../../media/icons/collapsed.svg");
}
.vs-dark .modelview-group-header-collapsible.collapsed,
.hc-black .modelview-group-header-collapsible.collapsed {
background-image: url("../../media/icons/collapsed_inverse.svg");
}
.modelview-group-cell {
padding-bottom: 5px;
display: table-cell;
}

View File

@@ -0,0 +1,183 @@
/*
https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs2015.css
*/
/*
* Visual Studio 2015 dark style
* Author: Nicolas LLOBERA <nllobera@gmail.com>
*/
modelview-dom-component .hljs-keyword,
modelview-dom-component .hljs-literal,
modelview-dom-component .hljs-symbol,
modelview-dom-component .hljs-name {
color: #569CD6;
}
modelview-dom-component .hljs-link {
color: #569CD6;
text-decoration: underline;
}
modelview-dom-component .hljs-built_in,
modelview-dom-component .hljs-type {
color: #4EC9B0;
}
modelview-dom-component .hljs-number,
modelview-dom-component .hljs-class {
color: #B8D7A3;
}
modelview-dom-component .hljs-string,
modelview-dom-component .hljs-meta-string {
color: #D69D85;
}
modelview-dom-component .hljs-regexp,
modelview-dom-component .hljs-template-tag {
color: #9A5334;
}
modelview-dom-component .hljs-subst,
modelview-dom-component .hljs-function,
modelview-dom-component .hljs-title,
modelview-dom-component .hljs-params,
modelview-dom-component .hljs-formula {
color: #DCDCDC;
}
modelview-dom-component .hljs-comment,
modelview-dom-component .hljs-quote {
color: #57A64A;
font-style: italic;
}
modelview-dom-component .hljs-doctag {
color: #608B4E;
}
modelview-dom-component .hljs-meta,
modelview-dom-component .hljs-meta-keyword,
modelview-dom-component .hljs-tag {
color: #9B9B9B;
}
modelview-dom-component .hljs-variable,
modelview-dom-component .hljs-template-variable {
color: #BD63C5;
}
modelview-dom-component .hljs-attr,
modelview-dom-component .hljs-attribute,
modelview-dom-component .hljs-builtin-name {
color: #9CDCFE;
}
modelview-dom-component .hljs-section {
color: gold;
}
modelview-dom-component .hljs-emphasis {
font-style: italic;
}
modelview-dom-component .hljs-strong {
font-weight: bold;
}
/*.hljs-code {
font-family:'Monospace';
}*/
modelview-dom-component .hljs-bullet,
modelview-dom-component .hljs-selector-tag,
modelview-dom-component .hljs-selector-id,
modelview-dom-component .hljs-selector-class,
modelview-dom-component .hljs-selector-attr,
modelview-dom-component .hljs-selector-pseudo {
color: #D7BA7D;
}
modelview-dom-component .hljs-addition {
background-color: #144212;
display: inline-block;
width: 100%;
}
modelview-dom-component .hljs-deletion {
background-color: #600;
display: inline-block;
width: 100%;
}
/*
From https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs.css
*/
/*
Visual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>
*/
/*
.vscode-light .hljs-function,
.vscode-light .hljs-params {
color: inherit;
}
.vscode-light .hljs-comment,
.vscode-light .hljs-quote,
.vscode-light .hljs-variable {
color: #008000;
}
.vscode-light .hljs-keyword,
.vscode-light .hljs-selector-tag,
.vscode-light .hljs-built_in,
.vscode-light .hljs-name,
.vscode-light .hljs-tag {
color: #00f;
}
.vscode-light .hljs-string,
.vscode-light .hljs-title,
.vscode-light .hljs-section,
.vscode-light .hljs-attribute,
.vscode-light .hljs-literal,
.vscode-light .hljs-template-tag,
.vscode-light .hljs-template-variable,
.vscode-light .hljs-type,
.vscode-light .hljs-addition {
color: #a31515;
}
.vscode-light .hljs-deletion,
.vscode-light .hljs-selector-attr,
.vscode-light .hljs-selector-pseudo,
.vscode-light .hljs-meta {
color: #2b91af;
}
.vscode-light .hljs-doctag {
color: #808080;
}
.vscode-light .hljs-attr {
color: #f00;
}
.vscode-light .hljs-symbol,
.vscode-light .hljs-bullet,
.vscode-light .hljs-link {
color: #00b0e8;
}
.vscode-light .hljs-emphasis {
font-style: italic;
}
.vscode-light .hljs-strong {
font-weight: bold;
}
*/

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g>
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.modelview-loadingComponent-container {
display: flex;
flex-direction: row;
justify-content: center;
}
.vs .modelview-loadingComponent-spinner {
content: url("loading.svg");
}
.vs-dark .modelview-loadingComponent-spinner,
.hc-black .modelview-loadingComponent-spinner {
content: url("loading_inverse.svg");
}
.modelview-loadingComponent-spinner {
height: 20px;
padding-top: 5px;
padding-bottom: 5px;
}
.modelview-loadingComponent-content-loading {
display: none;
}

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g style="fill:white;">
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,238 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
modelview-dom-component {
font-family: "Segoe WPC", "Segoe UI", "SFUIText-Light", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback";
font-size: 14px;
padding: 0 26px;
line-height: 22px;
word-wrap: break-word;
}
modelview-dom-component #code-csp-warning {
position: fixed;
top: 0;
right: 0;
color: white;
margin: 16px;
text-align: center;
font-size: 12px;
font-family: sans-serif;
background-color:#444444;
cursor: pointer;
padding: 6px;
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
}
modelview-dom-component #code-csp-warning:hover {
text-decoration: none;
background-color:#007acc;
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}
modelview-dom-component .scrollBeyondLastLine {
margin-bottom: calc(100vh - 22px);
}
modelview-dom-component .showEditorSelection .code-line {
position: relative;
}
modelview-dom-component .showEditorSelection .code-active-line:before,
modelview-dom-component .showEditorSelection .code-line:hover:before {
content: "";
display: block;
position: absolute;
top: 0;
left: -12px;
height: 100%;
}
modelview-dom-component .showEditorSelection li.code-active-line:before,
modelview-dom-component .showEditorSelection li.code-line:hover:before {
left: -30px;
}
modelview-dom-component .showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(0, 0, 0, 0.15);
}
modelview-dom-component .showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(0, 0, 0, 0.40);
}
modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-active-line:before
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(255, 255, 255, 0.4);
}
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-line:hover:before
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(255, 255, 255, 0.60);
}
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-line .code-line:hover:before
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
.hc-black modelview-dom-component .showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(255, 160, 0, 0.7);
}
.hc-black modelview-dom-component .showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(255, 160, 0, 1);
}
modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
modelview-dom-component img {
max-width: 100%;
max-height: 100%;
}
modelview-dom-component a, modelview-dom-component a:link{
text-decoration: none;
}
modelview-dom-component a:hover, modelview-dom-component a:link {
text-decoration: underline;
}
modelview-dom-component a:focus,
modelview-dom-component input:focus,
modelview-dom-component select:focus,
modelview-dom-component textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
modelview-dom-component hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
modelview-dom-component h1 {
padding-bottom: 0.3em;
line-height: 1.2;
border-bottom-width: 1px;
border-bottom-style: solid;
}
modelview-dom-component h1,
modelview-dom-component h2,
modelview-dom-component h3 {
font-weight: normal;
}
modelview-dom-component h1 code,
modelview-dom-component h2 code,
modelview-dom-component h3 code,
modelview-dom-component h4 code,
modelview-dom-component h5 code,
modelview-dom-component h6 code {
font-size: inherit;
line-height: auto;
}
modelview-dom-component table {
border-collapse: collapse;
}
modelview-dom-component table > thead > tr > th {
text-align: left;
border-bottom: 1px solid;
}
modelview-dom-component table > thead > tr > th,
modelview-dom-component table > thead > tr > td,
modelview-dom-component table > tbody > tr > th,
modelview-dom-component table > tbody > tr > td {
padding: 5px 10px;
}
modelview-dom-component table > tbody > tr + tr > td {
border-top: 1px solid;
}
modelview-dom-component blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
}
modelview-dom-component code {
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
font-size: 14px;
line-height: 19px;
}
modelview-dom-component .wordWrap pre {
white-space: pre-wrap;
}
modelview-dom-component .mac code {
font-size: 12px;
line-height: 18px;
}
modelview-dom-component pre:not(.hljs),
modelview-dom-component pre.hljs code > div {
padding: 16px;
border-radius: 3px;
overflow: auto;
}
/** Theming */
modelview-dom-component pre code {
color: var(--vscode-editor-foreground);
}
modelview-dom-component pre {
background-color: rgba(220, 220, 220, 0.4);
}
.vs-dark .monaco-workbench modelview-dom-component pre {
background-color: rgba(10, 10, 10, 0.4);
}
.hc-black .monaco-workbench modelview-dom-component pre {
background-color: rgb(0, 0, 0);
}
.hc-black .monaco-workbench modelview-dom-component h1 {
border-color: rgb(0, 0, 0);
}
modelview-dom-component table > thead > tr > th {
border-color: rgba(0, 0, 0, 0.69);
}
.vs-dark .monaco-workbench modelview-dom-component table > thead > tr > th {
border-color: rgba(255, 255, 255, 0.69);
}
modelview-dom-component h1,
modelview-dom-component hr,
modelview-dom-component table > tbody > tr + tr > td {
border-color: rgba(0, 0, 0, 0.18);
}
.vs-dark .monaco-workbench modelview-dom-component h1,
.vs-dark .monaco-workbench modelview-dom-component hr,
.vs-dark .monaco-workbench modelview-dom-component table > tbody > tr + tr > td {
border-color: rgba(255, 255, 255, 0.18);
}

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
modelview-content {
height: 100%;
width : 100%;
display: block;
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.model-view-container {
height: 100%;
width : 100%;
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.modelview-radiobutton-container {
align-items: flex-start;
}
.modelview-radiobutton-item {
align-self: flex-start ;
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.center-align
{
text-align: center;
}
.align-with-header
{
padding-left:3px !important;
}
.no-borders
{
border: none !important
}

View File

@@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.modelview-toolbar-container {
display: flex;
justify-content: flex-start;
line-height: 1.4em;
white-space: nowrap;
flex-wrap: wrap;
}
.modelview-toolbar-container.toolbar-horizontal {
flex-direction: row;
border-bottom-width: .5px;
border-bottom-style: solid;
box-sizing: border-box;
border-bottom-color: rgba(128, 128, 128, 0.35);
}
.modelview-toolbar-container.toolbar-vertical {
flex-direction: column;
}
.modelview-toolbar-container .modelview-toolbar-item {
flex: 0 0;
flex-direction: row;
display: flex;
padding-left: 10px;
}
.modelview-toolbar-container.toolbar-vertical .modelview-toolbar-item {
flex: 0 0;
flex-direction: row;
display: flex;
padding-left: 0px;
}
.modelview-toolbar-container .modelview-toolbar-title {
padding: 4px;
font-size: 13px;
cursor: pointer;
}
.modelview-toolbar-container .modelview-toolbar-component select,
.modelview-toolbar-container .modelview-toolbar-component .monaco-inputbox {
width: 200px;
height: 25px;
}
.modelview-toolbar-container .modelview-toolbar-component modelview-button .monaco-text-button.icon {
padding-left: 15px;
background-size: 11px;
margin-right: 0.3em;
}
/* Vertical button handling */
.modelview-toolbar-container.toolbar-vertical .modelview-toolbar-component modelview-button .monaco-text-button.icon {
padding: 20px 16px 20px 16px;
background-size: 20px;
margin-right: 0.3em;
background-position: 50% 50%;
}
.modelview-toolbar-container .modelview-toolbar-component modelview-button .monaco-text-button.active {
-ms-transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */
-webkit-transform: scale(1.272019649, 1.272019649);
-moz-transform: scale(1.272019649, 1.272019649);
-o-transform: scale(1.272019649, 1.272019649);
transform: scale(1.272019649, 1.272019649);
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.tree-component-node-tile {
display: flex;
}
.tree-component-node-tile .model-view-tree-node-item-icon {
width: 17px;
height: 17px;
flex-shrink: 0;
}
.tree-component-node-tile .model-view-tree-node-item-label {
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
modelview-webview-component {
height: 100%;
width : 100%;
display: block;
}

View File

@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, forwardRef, ComponentFactoryResolver, ViewChild,
ElementRef, OnInit, ChangeDetectorRef, ReflectiveInjector, Injector, ComponentRef
} from '@angular/core';
import { ComponentHostDirective } from 'sql/workbench/parts/dashboard/common/componentHost.directive';
import { error } from 'sql/base/common/log';
import { AngularDisposable } from 'sql/base/node/lifecycle';
import { IComponent, IComponentConfig, IComponentDescriptor, IModelStore, COMPONENT_CONFIG } from './interfaces';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import * as themeColors from 'vs/workbench/common/theme';
import { Registry } from 'vs/platform/registry/common/platform';
import { memoize } from 'vs/base/common/decorators';
import { generateUuid } from 'vs/base/common/uuid';
import { IBootstrapParams } from 'sql/platform/bootstrap/node/bootstrapService';
import { Event } from 'vs/base/common/event';
import { LayoutRequestParams } from 'sql/platform/dialog/dialogContainer.component';
const componentRegistry = <IComponentRegistry>Registry.as(Extensions.ComponentContribution);
export interface ModelComponentParams extends IBootstrapParams {
onLayoutRequested: Event<LayoutRequestParams>;
modelViewId: string;
}
@Component({
selector: 'model-component-wrapper',
template: `
<ng-template component-host>
</ng-template>
`
})
export class ModelComponentWrapper extends AngularDisposable implements OnInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
@memoize
public get guid(): string {
return generateUuid();
}
private _componentInstance: IComponent;
private _modelViewId: string;
@ViewChild(ComponentHostDirective) componentHost: ComponentHostDirective;
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver,
@Inject(forwardRef(() => ElementRef)) private _ref: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeref: ChangeDetectorRef,
@Inject(forwardRef(() => Injector)) private _injector: Injector,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IBootstrapParams) private _params: ModelComponentParams
) {
super();
if (_params && _params.onLayoutRequested) {
this._modelViewId = _params.modelViewId;
_params.onLayoutRequested(layoutParams => {
if (layoutParams && (layoutParams.alwaysRefresh || layoutParams.modelViewId === this._modelViewId)) {
this.layout();
}
});
}
}
ngOnInit() {
let self = this;
this._register(self.themeService.onDidColorThemeChange((event: IColorTheme) => {
self.updateTheme(event);
}));
}
ngAfterViewInit() {
this.updateTheme(this.themeService.getColorTheme());
if (this.componentHost) {
this.loadComponent();
}
this._changeref.detectChanges();
this.layout();
}
public layout(): void {
if (this.componentInstance && this.componentInstance.layout) {
this.componentInstance.layout();
}
}
public get id(): string {
return this._componentInstance.descriptor.id;
}
private get componentConfig(): IComponentConfig {
return {
descriptor: this.descriptor,
modelStore: this.modelStore
};
}
private get componentInstance(): IComponent {
if (!this._componentInstance) {
this.loadComponent();
}
return this._componentInstance;
}
private loadComponent(): void {
if (!this.descriptor || !this.descriptor.type) {
error('No descriptor or type defined for this component');
return;
}
let selector = componentRegistry.getCtorFromId(this.descriptor.type);
if (selector === undefined) {
error('No selector defined for type {0}', this.descriptor.type);
return;
}
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(selector);
let viewContainerRef = this.componentHost.viewContainerRef;
viewContainerRef.clear();
let injector = ReflectiveInjector.resolveAndCreate([{ provide: COMPONENT_CONFIG, useValue: this.componentConfig }], this._injector);
let componentRef: ComponentRef<IComponent>;
try {
componentRef = viewContainerRef.createComponent(componentFactory, 0, injector);
this._componentInstance = componentRef.instance;
this._componentInstance.descriptor = this.descriptor;
this._componentInstance.modelStore = this.modelStore;
this._changeref.detectChanges();
} catch (e) {
error('Error rendering component: {0}', e);
return;
}
let el = <HTMLElement>componentRef.location.nativeElement;
// set widget styles to conform to its box
el.style.overflow = 'hidden';
el.style.position = 'relative';
}
private updateTheme(theme: IColorTheme): void {
// TODO handle theming appropriately
let el = <HTMLElement>this._ref.nativeElement;
let borderColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true);
let backgroundColor = theme.getColor(colors.editorBackground, true);
let foregroundColor = theme.getColor(themeColors.SIDE_BAR_FOREGROUND, true);
let border = theme.getColor(colors.contrastBorder, true);
if (backgroundColor) {
el.style.backgroundColor = backgroundColor.toString();
}
if (foregroundColor) {
el.style.color = foregroundColor.toString();
}
}
}

View File

@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
import { Deferred } from 'sql/base/common/promise';
import { entries } from 'sql/base/common/objects';
class ComponentDescriptor implements IComponentDescriptor {
constructor(public readonly id: string, public readonly type: string) {
}
}
export class ModelStore implements IModelStore {
private static baseId = 0;
private _descriptorMappings: { [x: string]: IComponentDescriptor } = {};
private _componentMappings: { [x: string]: IComponent } = {};
private _componentActions: { [x: string]: Deferred<IComponent> } = {};
private _validationCallbacks: ((componentId: string) => Thenable<boolean>)[] = [];
constructor() {
}
public createComponentDescriptor(type: string, id: string): IComponentDescriptor {
let descriptor = new ComponentDescriptor(id, type);
this._descriptorMappings[id] = descriptor;
return descriptor;
}
getComponentDescriptor(id: string): IComponentDescriptor {
return this._descriptorMappings[id];
}
registerComponent(component: IComponent): void {
let id = component.descriptor.id;
this._componentMappings[id] = component;
this.runPendingActions(id, component);
}
unregisterComponent(component: IComponent): void {
let id = component.descriptor.id;
this._componentMappings[id] = undefined;
this._componentActions[id] = undefined;
this._descriptorMappings[id] = undefined;
// TODO notify model for cleanup
}
getComponent(componentId: string): IComponent {
return this._componentMappings[componentId];
}
eventuallyRunOnComponent<T>(componentId: string, action: (component: IComponent) => T): Promise<T> {
let component = this.getComponent(componentId);
if (component) {
return Promise.resolve(action(component));
} else {
return this.addPendingAction(componentId, action);
}
}
registerValidationCallback(callback: (componentId: string) => Thenable<boolean>): void {
this._validationCallbacks.push(callback);
}
validate(component: IComponent): Thenable<boolean> {
let componentId = entries(this._componentMappings).find(([id, mappedComponent]) => component === mappedComponent)[0];
return Promise.all(this._validationCallbacks.map(callback => callback(componentId))).then(validations => validations.every(validation => validation === true));
}
private addPendingAction<T>(componentId: string, action: (component: IComponent) => T): Promise<T> {
// We create a promise and chain it onto a tracking promise whose resolve method
// will only be called once the component is created
let deferredPromise = this._componentActions[componentId];
if (!deferredPromise) {
deferredPromise = new Deferred();
this._componentActions[componentId] = deferredPromise;
}
let promise = deferredPromise.promise.then((component) => {
return action(component);
});
return promise;
}
private runPendingActions(componentId: string, component: IComponent) {
let promiseTracker = this._componentActions[componentId];
if (promiseTracker) {
promiseTracker.resolve(component);
}
}
}

View File

@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Component, forwardRef, Input, OnInit, Inject, ChangeDetectorRef } from '@angular/core';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
import { memoize } from 'vs/base/common/decorators';
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
import { IModelView } from 'sql/platform/model/common/modelViewService';
import { ViewBase } from 'sql/workbench/electron-browser/modelComponents/viewBase';
import { IModelViewService } from 'sql/platform/modelComponents/common/modelViewService';
import * as azdata from 'azdata';
@Component({
selector: 'modelview-content',
template: `
<div *ngIf="rootDescriptor" style="width: 100%; height: 100%;">
<model-component-wrapper style="display: block; height: 100%" [descriptor]="rootDescriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
`
})
export class ModelViewContent extends ViewBase implements OnInit, IModelView {
@Input() private modelViewId: string;
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
private _onMessage = new Emitter<string>();
public readonly onMessage: Event<string> = this._onMessage.event;
private _onMessageDisposable: IDisposable;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IModelViewService) private modelViewService: IModelViewService
) {
super(changeRef);
}
ngOnInit() {
this.modelViewService.registerModelView(this);
this._register(addDisposableListener(window, EventType.RESIZE, e => {
this.layout();
}));
}
ngOnDestroy() {
this._onDestroy.fire();
super.ngOnDestroy();
}
public layout(): void {
this.changeRef.detectChanges();
}
public get id(): string {
return this.modelViewId;
}
@memoize
public get connection(): azdata.connection.Connection {
if (!this._commonService.connectionManagementService || !this._commonService.connectionManagementService.connectionInfo) {
return undefined;
}
let currentConnection = this._commonService.connectionManagementService.connectionInfo.connectionProfile;
let connection: azdata.connection.Connection = {
providerName: currentConnection.providerName,
connectionId: currentConnection.id,
options: currentConnection.options
};
return connection;
}
@memoize
public get serverInfo(): azdata.ServerInfo {
if (!this._commonService.connectionManagementService || !this._commonService.connectionManagementService.connectionInfo) {
return undefined;
}
return this._commonService.connectionManagementService.connectionInfo.serverInfo;
}
}

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ModelViewInput } from 'sql/workbench/electron-browser/modelComponents/modelViewInput';
import { ModelViewEditor } from 'sql/workbench/electron-browser/modelComponents/modelViewEditor';
// Model View editor registration
const viewModelEditorDescriptor = new EditorDescriptor(
ModelViewEditor,
ModelViewEditor.ID,
'ViewModel'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(ModelViewInput)]);

View File

@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/modelViewEditor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions } from 'vs/workbench/common/editor';
import * as DOM from 'vs/base/browser/dom';
import { ModelViewInput } from 'sql/workbench/electron-browser/modelComponents/modelViewInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
export class ModelViewEditor extends BaseEditor {
public static ID: string = 'workbench.editor.modelViewEditor';
private _editorFrame: HTMLElement;
private _content: HTMLElement;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(ModelViewEditor.ID, telemetryService, themeService, storageService);
}
/**
* Called to create the editor in the parent element.
*/
public createEditor(parent: HTMLElement): void {
this._editorFrame = parent;
this._content = document.createElement('div');
parent.appendChild(this._content);
}
/**
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
*/
public focus(): void {
}
public clearInput() {
this.hideOrRemoveModelViewContainer();
super.clearInput();
}
private hideOrRemoveModelViewContainer() {
if (this.input instanceof ModelViewInput) {
if (this.input.container) {
if (this.input.options && this.input.options.retainContextWhenHidden) {
this.input.container.style.visibility = 'hidden';
} else {
this.input.removeModelViewContainer();
this.input.container.style.visibility = 'hidden';
}
}
}
}
async setInput(input: ModelViewInput, options?: EditorOptions): Promise<void> {
if (this.input && this.input.matches(input)) {
return Promise.resolve(undefined);
}
this.hideOrRemoveModelViewContainer();
input.appendModelViewContainer();
input.container.style.visibility = 'visible';
this._content.setAttribute('aria-flowto', input.container.id);
await super.setInput(input, options, CancellationToken.None);
this.doUpdateContainer();
}
private doUpdateContainer() {
let modelViewInput = this.input as ModelViewInput;
const modelViewContainer = modelViewInput && modelViewInput.container;
if (modelViewContainer) {
const frameRect = this._editorFrame.getBoundingClientRect();
const containerRect = modelViewContainer.parentElement.getBoundingClientRect();
modelViewContainer.style.position = 'absolute';
modelViewContainer.style.top = `${frameRect.top}px`;
modelViewContainer.style.left = `${frameRect.left - containerRect.left}px`;
modelViewContainer.style.width = `${frameRect.width}px`;
modelViewContainer.style.height = `${frameRect.height}px`;
if (modelViewInput.dialogPane) {
modelViewInput.dialogPane.layout(true);
}
}
}
/**
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
* To be called when the container of this editor changes size.
*/
public layout(dimension: DOM.Dimension): void {
if (this.input instanceof ModelViewInput) {
if (this.input.container && this.input.dialogPane) {
this.doUpdateContainer();
// todo: layout this.input.dialogPane (Github issue: #1484)
}
}
}
}

View File

@@ -0,0 +1,160 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
import * as DOM from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DialogPane } from 'sql/platform/dialog/dialogPane';
import { Emitter, Event } from 'vs/base/common/event';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
export class ModelViewInputModel extends EditorModel {
private dirty: boolean;
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
get onDidChangeDirty(): Event<void> { return this._onDidChangeDirty.event; }
constructor(public readonly modelViewId, private readonly handle: number, private saveHandler?: ModeViewSaveHandler) {
super();
this.dirty = false;
}
get isDirty(): boolean {
return this.dirty;
}
public setDirty(dirty: boolean): void {
if (this.dirty === dirty) {
return;
}
this.dirty = dirty;
this._onDidChangeDirty.fire();
}
save(): Promise<boolean> {
if (this.saveHandler) {
return Promise.resolve(this.saveHandler(this.handle));
}
return Promise.resolve(true);
}
}
export class ModelViewInput extends EditorInput {
public static ID: string = 'workbench.editorinputs.ModelViewEditorInput';
private _container: HTMLElement;
private _dialogPaneContainer: HTMLElement;
private _dialogPane: DialogPane;
constructor(private _title: string, private _model: ModelViewInputModel,
private _options: azdata.ModelViewEditorOptions,
@IInstantiationService private _instantiationService: IInstantiationService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super();
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
this._container = document.createElement('div');
this._container.id = `modelView-${_model.modelViewId}`;
this.layoutService.getContainer(Parts.EDITOR_PART).appendChild(this._container);
}
public get title(): string {
return this._title;
}
public get modelViewId(): string {
return this._model.modelViewId;
}
public getTypeId(): string {
return 'ModelViewEditorInput';
}
public resolve(refresh?: boolean): Promise<IEditorModel> {
return undefined;
}
public getName(): string {
return this._title;
}
public get container(): HTMLElement {
return this._container;
}
public appendModelViewContainer(): void {
if (!this._dialogPane) {
this.createDialogPane();
}
if (!this._container.contains(this._dialogPaneContainer)) {
this._container.appendChild(this._dialogPaneContainer);
}
}
public removeModelViewContainer(): void {
if (this._dialogPaneContainer) {
this._container.removeChild(this._dialogPaneContainer);
}
}
private createDialogPane(): void {
this._dialogPaneContainer = DOM.$('div.model-view-container');
this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService, false);
this._dialogPane.createBody(this._dialogPaneContainer);
}
public get dialogPane(): DialogPane {
return this._dialogPane;
}
public get options(): azdata.ModelViewEditorOptions {
return this._options;
}
/**
* An editor that is dirty will be asked to be saved once it closes.
*/
isDirty(): boolean {
return this._model.isDirty;
}
/**
* Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
*/
confirmSave(): Promise<ConfirmResult> {
// TODO #2530 support save on close / confirm save. This is significantly more work
// as we need to either integrate with textFileService (seems like this isn't viable)
// or register our own complimentary service that handles the lifecycle operations such
// as close all, auto save etc.
return Promise.resolve(ConfirmResult.DONT_SAVE);
}
/**
* Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
*/
save(): Promise<boolean> {
return this._model.save();
}
public dispose(): void {
if (this._dialogPane) {
this._dialogPane.dispose();
}
if (this._container) {
this._container.remove();
this._container = undefined;
}
if (this._model) {
this._model.dispose();
}
super.dispose();
}
}

View File

@@ -0,0 +1,205 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import * as nls from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { EditorOptions } from 'vs/workbench/common/editor';
import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Configuration } from 'vs/editor/browser/config/configuration';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
/**
* Extension of TextResourceEditor that is always readonly rather than only with non UntitledInputs
*/
export class QueryTextEditor extends BaseTextEditor {
public static ID = 'modelview.editors.textEditor';
private _dimension: DOM.Dimension;
private _config: editorCommon.IConfiguration;
private _minHeight: number = 0;
private _maxHeight: number = 4000;
private _selected: boolean;
private _hideLineNumbers: boolean;
private _editorWorkspaceConfig;
private _scrollbarHeight: number;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IThemeService themeService: IThemeService,
@ITextFileService textFileService: ITextFileService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService protected editorService: IEditorService,
@IWindowService windowService: IWindowService,
@IConfigurationService private workspaceConfigurationService: IConfigurationService,
@IAccessibilityService private accessibilityService: IAccessibilityService
) {
super(
QueryTextEditor.ID, telemetryService, instantiationService, storageService,
configurationService, themeService, textFileService, editorService, editorGroupService, windowService);
}
public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
return this.instantiationService.createInstance(StandaloneCodeEditor, parent, configuration);
}
protected getConfigurationOverrides(): IEditorOptions {
const options = super.getConfigurationOverrides();
if (this.input) {
options.inDiffEditor = true;
options.scrollBeyondLastLine = false;
options.folding = false;
options.renderIndentGuides = false;
options.rulers = [];
options.glyphMargin = true;
options.minimap = {
enabled: false
};
options.overviewRulerLanes = 0;
options.overviewRulerBorder = false;
options.hideCursorInOverviewRuler = true;
if (!this._selected) {
options.renderLineHighlight = 'none';
options.parameterHints = { enabled: false };
options.matchBrackets = false;
}
if (this._hideLineNumbers) {
options.lineNumbers = 'off';
}
}
return options;
}
setInput(input: UntitledEditorInput, options: EditorOptions): Promise<void> {
return super.setInput(input, options, CancellationToken.None)
.then(() => this.input.resolve()
.then(editorModel => editorModel.load())
.then(editorModel => this.getControl().setModel((<ResourceEditorModel>editorModel).textEditorModel)));
}
protected getAriaLabel(): string {
return nls.localize('queryTextEditorAriaLabel', 'modelview code editor for view model.');
}
public layout(dimension?: DOM.Dimension) {
if (dimension) {
this._dimension = dimension;
}
this.getControl().layout(dimension);
}
public setWidth(width: number) {
if (this._dimension) {
this._dimension.width = width;
this.layout();
}
}
public setHeight(height: number) {
if (this._dimension) {
this._dimension.height = height;
this.layout(this._dimension);
}
}
public get scrollHeight(): number {
let editorWidget = this.getControl() as ICodeEditor;
return editorWidget.getScrollHeight();
}
public setHeightToScrollHeight(configChanged?: boolean): void {
let editorWidget = this.getControl() as ICodeEditor;
if (!this._config) {
this._config = new Configuration(true, undefined, editorWidget.getDomNode(), this.accessibilityService);
this._scrollbarHeight = this._config.editor.viewInfo.scrollbar.horizontalScrollbarSize;
}
let editorWidgetModel = editorWidget.getModel();
if (!editorWidgetModel) {
// Not ready yet
return;
}
let lineCount = editorWidgetModel.getLineCount();
// Need to also keep track of lines that wrap; if we just keep into account line count, then the editor's height would not be
// tall enough and we would need to show a scrollbar. Unfortunately, it looks like there isn't any metadata saved in a ICodeEditor
// around max column length for an editor (which we could leverage to see if we need to loop through every line to determine
// number of lines that wrap). Finally, viewportColumn is calculated on editor resizing automatically; we can use it to ensure
// that the viewportColumn will always be greater than any character's column in an editor.
let numberWrappedLines = 0;
let shouldAddHorizontalScrollbarHeight = false;
if (!this._editorWorkspaceConfig || configChanged) {
this._editorWorkspaceConfig = this.workspaceConfigurationService.getValue('editor');
}
let wordWrapEnabled: boolean = this._editorWorkspaceConfig && this._editorWorkspaceConfig['wordWrap'] && this._editorWorkspaceConfig['wordWrap'] === 'on' ? true : false;
if (wordWrapEnabled) {
for (let line = 1; line <= lineCount; line++) {
// 4 columns is equivalent to the viewport column width and the edge of the editor
if (editorWidgetModel.getLineMaxColumn(line) >= this._config.editor.layoutInfo.viewportColumn + 4) {
numberWrappedLines += Math.ceil(editorWidgetModel.getLineMaxColumn(line) / this._config.editor.layoutInfo.viewportColumn);
}
}
} else {
for (let line = 1; line <= lineCount; line++) {
// The horizontal scrollbar always appears 1 column past the viewport column when word wrap is disabled
if (editorWidgetModel.getLineMaxColumn(line) >= this._config.editor.layoutInfo.viewportColumn + 1) {
shouldAddHorizontalScrollbarHeight = true;
break;
}
}
}
let editorHeightUsingLines = this._config.editor.lineHeight * (lineCount + numberWrappedLines);
let editorHeightUsingMinHeight = Math.max(Math.min(editorHeightUsingLines, this._maxHeight), this._minHeight);
editorHeightUsingMinHeight = shouldAddHorizontalScrollbarHeight ? editorHeightUsingMinHeight + this._scrollbarHeight : editorHeightUsingMinHeight;
this.setHeight(editorHeightUsingMinHeight);
}
public setMinimumHeight(height: number): void {
this._minHeight = height;
}
public setMaximumHeight(height: number): void {
this._maxHeight = height;
}
public toggleEditorSelected(selected: boolean): void {
this._selected = selected;
this.refreshEditorConfiguration();
}
public set hideLineNumbers(value: boolean) {
this._hideLineNumbers = value;
this.refreshEditorConfiguration();
}
private refreshEditorConfiguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {
if (!this.getControl()) {
return;
}
const editorConfiguration = this.computeConfiguration(configuration);
let editorSettingsToApply = editorConfiguration;
this.getControl().updateOptions(editorSettingsToApply);
}
}

View File

@@ -0,0 +1,118 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/radioButton';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { RadioButton } from 'sql/base/browser/ui/radioButton/radioButton';
@Component({
selector: 'modelview-radioButton',
template: `
<div #input class="modelview-radiobutton-container">
</div>
`
})
export default class RadioButtonComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _input: RadioButton;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
this._input = new RadioButton(this._inputContainer.nativeElement, {
label: this.label
});
this._register(this._input);
this._register(this._input.onClicked(e => {
this.checked = this._input.checked;
this.fireEvent({
eventType: ComponentEventType.onDidClick,
args: e
});
}));
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._input.name = this.name;
this._input.value = this.value;
this._input.label = this.label;
this._input.enabled = this.enabled;
this._input.checked = this.checked;
}
// CSS-bound properties
public get checked(): boolean {
return this.getPropertyOrDefault<azdata.RadioButtonProperties, boolean>((props) => props.checked, false);
}
public set checked(newValue: boolean) {
this.setPropertyFromUI<azdata.RadioButtonProperties, boolean>((properties, value) => { properties.checked = value; }, newValue);
}
public set value(newValue: string) {
this.setPropertyFromUI<azdata.RadioButtonProperties, string>((properties, value) => { properties.value = value; }, newValue);
}
public get value(): string {
return this.getPropertyOrDefault<azdata.RadioButtonProperties, string>((props) => props.value, '');
}
public getLabel(): string {
return this.label;
}
public get label(): string {
return this.getPropertyOrDefault<azdata.RadioButtonProperties, string>((props) => props.label, '');
}
public set label(newValue: string) {
this.setPropertyFromUI<azdata.RadioButtonProperties, string>((properties, label) => { properties.label = label; }, newValue);
}
public get name(): string {
return this.getPropertyOrDefault<azdata.RadioButtonProperties, string>((props) => props.name, '');
}
public set name(newValue: string) {
this.setPropertyFromUI<azdata.RadioButtonProperties, string>((properties, label) => { properties.name = label; }, newValue);
}
}

View File

@@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/flexContainer';
import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy } from '@angular/core';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { FlexItemLayout, SplitViewLayout } from 'azdata';
import { FlexItem } from './flexContainer.component';
import { ContainerBase, ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { Event } from 'vs/base/common/event';
import { SplitView, Orientation, Sizing, IView } from 'vs/base/browser/ui/splitview/splitview';
class SplitPane implements IView {
orientation: Orientation;
element: HTMLElement;
minimumSize: number;
maximumSize: number;
onDidChange: Event<number> = Event.None;
size: number;
component: ComponentBase;
layout(size: number): void {
this.size = size;
try {
if (this.orientation === Orientation.VERTICAL) {
this.component.updateProperty('height', size);
}
else {
this.component.updateProperty('width', size);
}
} catch { }
}
}
@Component({
template: `
<div *ngIf="items" class="splitViewContainer" [style.flexFlow]="flexFlow" [style.justifyContent]="justifyContent" [style.position]="position"
[style.alignItems]="alignItems" [style.alignContent]="alignContent" [style.height]="height" [style.width]="width">
<div *ngFor="let item of items" [style.flex]="getItemFlex(item)" [style.textAlign]="textAlign" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
`
})
export default class SplitViewContainer extends ContainerBase<FlexItemLayout> implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _flexFlow: string;
private _justifyContent: string;
private _alignItems: string;
private _alignContent: string;
private _textAlign: string;
private _height: string;
private _width: string;
private _position: string;
private _splitView: SplitView;
private _orientation: Orientation;
private _splitViewHeight: number;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
this._flexFlow = ''; // default
this._justifyContent = ''; // default
this._orientation = Orientation.VERTICAL; // default
}
ngOnInit(): void {
this.baseInit();
}
ngOnDestroy(): void {
this.baseDestroy();
}
ngAfterViewInit(): void {
this._splitView = this._register(new SplitView(this._el.nativeElement, { orientation: this._orientation }));
}
private GetCorrespondingView(component: IComponent, orientation: Orientation): IView {
let c = component as ComponentBase;
let basicView: SplitPane = new SplitPane();
basicView.orientation = orientation;
basicView.element = c.getHtml(),
basicView.component = c;
basicView.minimumSize = 50;
basicView.maximumSize = Number.MAX_VALUE;
return basicView;
}
/// IComponent implementation
public setLayout(layout: SplitViewLayout): void {
this._flexFlow = layout.flexFlow ? layout.flexFlow : '';
this._justifyContent = layout.justifyContent ? layout.justifyContent : '';
this._alignItems = layout.alignItems ? layout.alignItems : '';
this._alignContent = layout.alignContent ? layout.alignContent : '';
this._textAlign = layout.textAlign ? layout.textAlign : '';
this._position = layout.position ? layout.position : '';
this._height = this.convertSize(layout.height);
this._width = this.convertSize(layout.width);
this._orientation = layout.orientation.toLowerCase() === 'vertical' ? Orientation.VERTICAL : Orientation.HORIZONTAL;
this._splitViewHeight = this.convertSizeToNumber(layout.splitViewHeight);
if (this._componentWrappers) {
this._componentWrappers.forEach(item => {
let component = item.modelStore.getComponent(item.descriptor.id);
item.modelStore.validate(component).then(value => {
if (value === true) {
let view = this.GetCorrespondingView(component, this._orientation);
this._splitView.addView(view, Sizing.Distribute);
}
else {
console.log('Could not add views inside split view container');
}
});
});
}
this._splitView.layout(this._splitViewHeight);
}
// CSS-bound properties
public get flexFlow(): string {
return this._flexFlow;
}
public get justifyContent(): string {
return this._justifyContent;
}
public get alignItems(): string {
return this._alignItems;
}
public get height(): string {
return this._height;
}
public get width(): string {
return this._width;
}
public get alignContent(): string {
return this._alignContent;
}
public get textAlign(): string {
return this._textAlign;
}
public get position(): string {
return this._position;
}
public get orientation(): string {
return this._orientation.toString();
}
private getItemFlex(item: FlexItem): string {
return item.config ? item.config.flex : '1 1 auto';
}
private getItemOrder(item: FlexItem): number {
return item.config ? item.config.order : 0;
}
private getItemStyles(item: FlexItem): { [key: string]: string } {
return item.config && item.config.CSSStyles ? item.config.CSSStyles : {};
}
}

View File

@@ -0,0 +1,202 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/table';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { Table } from 'sql/base/browser/ui/table/table';
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { getContentHeight, getContentWidth, Dimension } from 'vs/base/browser/dom';
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
@Component({
selector: 'modelview-table',
template: `
<div #table style="width: 100%;height:100%" [style.font-size]="fontSize"></div>
`
})
export default class TableComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _table: Table<Slick.SlickData>;
private _tableData: TableDataView<Slick.SlickData>;
private _tableColumns;
@ViewChild('table', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
transformColumns(columns: string[] | azdata.TableColumn[]): Slick.Column<any>[] {
let tableColumns: any[] = <any[]>columns;
if (tableColumns) {
return (<any[]>columns).map(col => {
if (col.value) {
return <Slick.Column<any>>{
name: col.value,
id: col.value,
field: col.value,
width: col.width,
cssClass: col.cssClass,
headerCssClass: col.headerCssClass,
toolTip: col.toolTip
};
} else {
return <Slick.Column<any>>{
name: <string>col,
id: <string>col,
field: <string>col
};
}
});
} else {
return (<string[]>columns).map(col => {
return <Slick.Column<any>>{
name: col,
id: col,
field: col
};
});
}
}
public static transformData(rows: string[][], columns: any[]): { [key: string]: string }[] {
if (rows && columns) {
return rows.map(row => {
let object: { [key: string]: string } = {};
if (row.forEach) {
row.forEach((val, index) => {
let columnName: string = (columns[index].value) ? columns[index].value : <string>columns[index];
object[columnName] = val;
});
}
return object;
});
} else {
return [];
}
}
ngAfterViewInit(): void {
if (this._inputContainer) {
this._tableData = new TableDataView<Slick.SlickData>();
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
enableCellNavigation: true,
forceFitColumns: true
};
this._table = new Table<Slick.SlickData>(this._inputContainer.nativeElement, { dataProvider: this._tableData, columns: this._tableColumns }, options);
this._table.setData(this._tableData);
this._table.setSelectionModel(new RowSelectionModel({ selectActiveRow: true }));
this._register(this._table);
this._register(attachTableStyler(this._table, this.themeService));
this._register(this._table.onSelectedRowsChanged((e, data) => {
this.selectedRows = data.rows;
this.fireEvent({
eventType: ComponentEventType.onSelectedRowChanged,
args: e
});
}));
}
}
public validate(): Thenable<boolean> {
return super.validate().then(valid => {
// TODO: table validation?
return valid;
});
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public layout(): void {
this.layoutTable();
super.layout();
}
private layoutTable(): void {
let width: number = this.convertSizeToNumber(this.width);
let height: number = this.convertSizeToNumber(this.height);
this._table.layout(new Dimension(
width && width > 0 ? width : getContentWidth(this._inputContainer.nativeElement),
height && height > 0 ? height : getContentHeight(this._inputContainer.nativeElement)));
}
public setLayout(): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._tableData.clear();
this._tableData.push(TableComponent.transformData(this.data, this.columns));
this._tableColumns = this.transformColumns(this.columns);
this._table.columns = this._tableColumns;
this._table.setData(this._tableData);
if (this.selectedRows) {
this._table.setSelectedRows(this.selectedRows);
}
this.layoutTable();
this.validate();
}
// CSS-bound properties
public get data(): any[][] {
return this.getPropertyOrDefault<azdata.TableComponentProperties, any[]>((props) => props.data, []);
}
public set data(newValue: any[][]) {
this.setPropertyFromUI<azdata.TableComponentProperties, any[][]>((props, value) => props.data = value, newValue);
}
public get columns(): string[] {
return this.getPropertyOrDefault<azdata.TableComponentProperties, string[]>((props) => props.columns, []);
}
public get fontSize(): number | string {
return this.getPropertyOrDefault<azdata.TableComponentProperties, number | string>((props) => props.fontSize, '');
}
public set columns(newValue: string[]) {
this.setPropertyFromUI<azdata.TableComponentProperties, string[]>((props, value) => props.columns = value, newValue);
}
public get selectedRows(): number[] {
return this.getPropertyOrDefault<azdata.TableComponentProperties, number[]>((props) => props.selectedRows, []);
}
public set selectedRows(newValue: number[]) {
this.setPropertyFromUI<azdata.TableComponentProperties, number[]>((props, value) => props.selectedRows = value, newValue);
}
}

View File

@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/radioButton';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
OnDestroy, AfterViewInit, ElementRef, SecurityContext
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { SafeHtml, DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'modelview-text',
template: `
<p [style.width]="getWidth()" [innerHTML]="getValue()"></p>`
})
export default class TextComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => DomSanitizer)) private _domSanitizer: DomSanitizer) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public set value(newValue: string) {
this.setPropertyFromUI<azdata.TextComponentProperties, string>((properties, value) => { properties.value = value; }, newValue);
}
public get value(): string {
return this.getPropertyOrDefault<azdata.TextComponentProperties, string>((props) => props.value, '');
}
public getValue(): SafeHtml {
let links = this.getPropertyOrDefault<azdata.TextComponentProperties, azdata.LinkArea[]>((props) => props.links, []);
let text = this._domSanitizer.sanitize(SecurityContext.HTML, this.value);
if (links.length !== 0) {
for (let i: number = 0; i < links.length; i++) {
let link = links[i];
let linkTag = `<a href="${this._domSanitizer.sanitize(SecurityContext.URL, link.url)}" tabIndex="0" target="blank">${this._domSanitizer.sanitize(SecurityContext.HTML, link.text)}</a>`;
text = text.replace(`{${i}}`, linkTag);
}
}
return text;
}
}

View File

@@ -0,0 +1,119 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/toolbarLayout';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import { Orientation, ToolbarLayout } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { ContainerBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
export interface ToolbarItemConfig {
title?: string;
toolbarSeparatorAfter?: boolean;
}
export class ToolbarItem {
constructor(public descriptor: IComponentDescriptor, public config: ToolbarItemConfig) { }
}
@Component({
selector: 'modelview-toolbarContainer',
template: `
<div #container *ngIf="items" [class]="toolbarClass" >
<ng-container *ngFor="let item of items">
<div class="modelview-toolbar-item" [style.paddingTop]="paddingTop">
<div *ngIf="shouldShowTitle(item)" class="modelview-toolbar-title" >
{{getItemTitle(item)}}
</div>
<div class="modelview-toolbar-component">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore" >
</model-component-wrapper>
</div>
<div *ngIf="shouldShowToolbarSeparator(item)" class="taskbarSeparator" >
</div>
</div>
</ng-container>
</div>
`
})
export default class ToolbarContainer extends ContainerBase<ToolbarItemConfig> implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
private _orientation: Orientation;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
this._orientation = Orientation.Horizontal;
}
ngOnInit(): void {
this.baseInit();
}
ngOnDestroy(): void {
this.baseDestroy();
}
ngAfterViewInit(): void {
}
/// IComponent implementation
public setLayout(layout: ToolbarLayout): void {
this._orientation = layout.orientation ? layout.orientation : Orientation.Horizontal;
this.layout();
}
public getItemTitle(item: ToolbarItem): string {
let itemConfig = item.config;
return itemConfig ? itemConfig.title : '';
}
public shouldShowTitle(item: ToolbarItem): boolean {
return this.hasTitle(item) && this.isHorizontal();
}
public shouldShowToolbarSeparator(item: ToolbarItem): boolean {
if (!item || !item.config) {
return false;
}
return item.config.toolbarSeparatorAfter;
}
private hasTitle(item: ToolbarItem): boolean {
return item && item.config && item.config.title !== undefined;
}
public get paddingTop(): string {
return this.isHorizontal() ? '' : '';
}
public get toolbarClass(): string {
let classes = ['modelview-toolbar-container'];
if (this.isHorizontal()) {
classes.push('toolbar-horizontal');
} else {
classes.push('toolbar-vertical');
}
return classes.join(' ');
}
private isHorizontal(): boolean {
return this._orientation === Orientation.Horizontal;
}
}

View File

@@ -0,0 +1,163 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/treeComponent';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
import { TreeComponentRenderer } from 'sql/workbench/electron-browser/modelComponents/treeComponentRenderer';
import { TreeComponentDataSource } from 'sql/workbench/electron-browser/modelComponents/treeDataSource';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITreeComponentItem } from 'sql/workbench/common/views';
import { TreeViewDataProvider } from 'sql/workbench/electron-browser/modelComponents/treeViewDataProvider';
import { getContentHeight, getContentWidth } from 'vs/base/browser/dom';
class Root implements ITreeComponentItem {
label = {
label: 'root'
};
handle = '0';
parentHandle = null;
collapsibleState = 2;
children = void 0;
options = undefined;
}
@Component({
selector: 'modelview-tree',
template: `
<div #input style="width: 100%;height:100%"></div>
`
})
export default class TreeComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _tree: Tree;
private _treeRenderer: TreeComponentRenderer;
private _dataProvider: TreeViewDataProvider;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
this.createTreeControl();
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
public setDataProvider(handle: number, componentId: string, context: any): any {
this._dataProvider = new TreeViewDataProvider(handle, componentId, context);
this.createTreeControl();
}
public refreshDataProvider(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeComponentItem }): void {
if (this._dataProvider) {
this._dataProvider.getItemsToRefresh(itemsToRefreshByHandle);
}
if (this._tree) {
for (const item of Object.values(itemsToRefreshByHandle)) {
this._tree.refresh(<ITreeComponentItem>item);
}
}
}
private createTreeControl(): void {
if (!this._tree && this._dataProvider) {
const dataSource = this._instantiationService.createInstance(TreeComponentDataSource, this._dataProvider);
const renderer = this._instantiationService.createInstance(TreeComponentRenderer, this._dataProvider, this.themeService, { withCheckbox: this.withCheckbox });
this._treeRenderer = renderer;
const controller = new DefaultController();
const filter = new DefaultFilter();
const sorter = undefined;
const dnd = undefined;
const accessibilityProvider = new DefaultAccessibilityProvider();
this._tree = new Tree(this._inputContainer.nativeElement,
{ dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
{
indentPixels: 10,
twistiePixels: 20,
ariaLabel: 'Tree Node'
});
this._tree.setInput(new Root());
this._tree.domFocus();
this._register(this._tree);
this._register(attachListStyler(this._tree, this.themeService));
this._register(this._tree.onDidChangeSelection(e => {
this._dataProvider.onNodeSelected(e.selection);
}));
this._tree.refresh();
this.layout();
}
}
/// IComponent implementation
public layout(): void {
if (this._tree) {
this.layoutTree();
this._tree.refresh();
}
super.layout();
}
private layoutTree(): void {
let width: number = this.convertSizeToNumber(this.width);
let height: number = this.convertSizeToNumber(this.height);
this._tree.layout(
height && height > 0 ? height : getContentHeight(this._inputContainer.nativeElement),
width && width > 0 ? width : getContentWidth(this._inputContainer.nativeElement));
}
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._treeRenderer.options.withCheckbox = this.withCheckbox;
}
public get withCheckbox(): boolean {
return this.getPropertyOrDefault<azdata.TreeProperties, boolean>((props) => props.withCheckbox, false);
}
public set withCheckbox(newValue: boolean) {
this.setPropertyFromUI<azdata.TreeProperties, boolean>((properties, value) => { properties.withCheckbox = value; }, newValue);
}
}

View File

@@ -0,0 +1,180 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { LIGHT } from 'vs/platform/theme/common/themeService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { ITreeComponentItem } from 'sql/workbench/common/views';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { TreeViewDataProvider } from './treeViewDataProvider';
import { URI } from 'vs/base/common/uri';
export enum TreeCheckboxState {
Intermediate = 0,
Checked = 1,
Unchecked = 2
}
export class TreeDataTemplate extends Disposable {
root: HTMLElement;
label: HTMLSpanElement;
icon: HTMLElement;
private _checkbox: HTMLInputElement;
model: ITreeComponentItem;
private _onChange = new Emitter<boolean>();
public readonly onChange: Event<boolean> = this._onChange.event;
public set checkbox(input: HTMLInputElement) {
this._checkbox = input;
this.handleOnChange(this._checkbox, () => {
this._onChange.fire(this._checkbox.checked);
if (this.model && this.model.onCheckedChanged) {
this.model.onCheckedChanged(this._checkbox.checked);
}
});
}
public get checkboxState(): TreeCheckboxState {
if (this._checkbox.indeterminate) {
return TreeCheckboxState.Intermediate;
} else {
return this.checkbox.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
}
}
public set checkboxState(value: TreeCheckboxState) {
if (this.checkboxState !== value) {
switch (value) {
case TreeCheckboxState.Checked:
this._checkbox.indeterminate = false;
this._checkbox.checked = true;
break;
case TreeCheckboxState.Unchecked:
this._checkbox.indeterminate = false;
this._checkbox.checked = false;
break;
case TreeCheckboxState.Intermediate:
this._checkbox.indeterminate = true;
break;
default:
break;
}
}
}
public set enableCheckbox(value: boolean) {
if (value === undefined) {
value = true;
}
this._checkbox.disabled = !value;
}
public get checkbox(): HTMLInputElement {
return this._checkbox;
}
protected handleOnChange(domNode: HTMLElement, listener: (e: Event<void>) => void): void {
this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener));
}
}
/**
* Renders the tree items.
* Uses the dom template to render connection groups and connections.
*/
export class TreeComponentRenderer extends Disposable implements IRenderer {
public static DEFAULT_TEMPLATE = 'DEFAULT_TEMPLATE';
public static DEFAULT_HEIGHT = 20;
constructor(
private _dataProvider: TreeViewDataProvider,
private themeService: IWorkbenchThemeService,
public options?: { withCheckbox: boolean }
) {
super();
}
/**
* Returns the element's height in the tree, in pixels.
*/
public getHeight(tree: ITree, element: any): number {
return TreeComponentRenderer.DEFAULT_HEIGHT;
}
/**
* Returns a template ID for a given element.
*/
public getTemplateId(tree: ITree, element: any): string {
return TreeComponentRenderer.DEFAULT_TEMPLATE;
}
/**
* Render template in a dom element based on template id
*/
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
if (templateId === TreeComponentRenderer.DEFAULT_TEMPLATE) {
const nodeTemplate: TreeDataTemplate = new TreeDataTemplate();
nodeTemplate.root = dom.append(container, dom.$('.tree-component-node-tile'));
nodeTemplate.icon = dom.append(nodeTemplate.root, dom.$('div.model-view-tree-node-item-icon'));
if (this.options && this.options.withCheckbox) {
let checkboxWrapper = dom.append(nodeTemplate.root, dom.$('div.checkboxWrapper'));
nodeTemplate.checkbox = dom.append(checkboxWrapper, dom.$<HTMLInputElement>('input.checkbox', { type: 'checkbox' }));
}
nodeTemplate.label = dom.append(nodeTemplate.root, dom.$('div.model-view-tree-node-item-label'));
return nodeTemplate;
}
}
/**
* Render a element, given an object bag returned by the template
*/
public renderElement(tree: ITree, element: ITreeComponentItem, templateId: string, templateData: TreeDataTemplate): void {
const icon = this.themeService.getTheme().type === LIGHT ? element.icon : element.iconDark;
const iconUri = icon ? URI.revive(icon) : null;
templateData.icon.style.backgroundImage = iconUri ? `url('${iconUri.toString(true)}')` : '';
templateData.icon.style.backgroundRepeat = 'no-repeat';
templateData.icon.style.backgroundPosition = 'center';
dom.toggleClass(templateData.icon, 'model-view-tree-node-item-icon', !!icon);
if (element) {
element.onCheckedChanged = (checked: boolean) => {
this._dataProvider.onNodeCheckedChanged(element.handle, checked);
};
templateData.model = element;
}
if (templateId === TreeComponentRenderer.DEFAULT_TEMPLATE) {
this.renderNode(element, templateData);
}
}
private renderNode(treeNode: ITreeComponentItem, templateData: TreeDataTemplate): void {
let label = treeNode.label;
templateData.label.textContent = label.label;
templateData.root.title = label.label;
templateData.checkboxState = this.getCheckboxState(treeNode);
templateData.enableCheckbox = treeNode.enabled;
}
private getCheckboxState(treeNode: ITreeComponentItem): TreeCheckboxState {
if (treeNode.checked === undefined) {
return TreeCheckboxState.Intermediate;
} else {
return treeNode.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
}
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
this.dispose();
// no op
}
}

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbench/common/views';
import { TreeItemCollapsibleState } from 'vs/workbench/common/views';
/**
* Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
*/
export class TreeComponentDataSource implements IDataSource {
/**
*
*/
constructor(
private _dataProvider: IModelViewTreeViewDataProvider) {
}
/**
* Returns the unique identifier of the given element.
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, node: ITreeComponentItem): string {
return node.handle;
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, node: ITreeComponentItem): boolean {
return this._dataProvider !== undefined && node.collapsibleState !== TreeItemCollapsibleState.None;
}
/**
* Returns the element's children as an array in a promise.
*/
public getChildren(tree: ITree, node: ITreeComponentItem): Promise<any> {
if (this._dataProvider) {
if (node && node.handle === '0') {
return this._dataProvider.getChildren(undefined);
} else {
return this._dataProvider.getChildren(node);
}
}
return Promise.resolve([]);
}
public getParent(tree: ITree, node: any): Promise<any> {
return Promise.resolve(null);
}
public shouldAutoexpand(tree: ITree, node: ITreeComponentItem): boolean {
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
}
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtHostModelViewTreeViewsShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbench/common/views';
import { INotificationService } from 'vs/platform/notification/common/notification';
import * as vsTreeView from 'vs/workbench/api/browser/mainThreadTreeViews';
export class TreeViewDataProvider extends vsTreeView.TreeViewDataProvider implements IModelViewTreeViewDataProvider {
constructor(handle: number, treeViewId: string,
context: IExtHostContext,
notificationService?: INotificationService
) {
super(`${handle}-${treeViewId}`, context.getProxy(SqlExtHostContext.ExtHostModelViewTreeViews), notificationService);
}
onNodeCheckedChanged(treeItemHandle?: string, checked?: boolean) {
(<ExtHostModelViewTreeViewsShape>this._proxy).$onNodeCheckedChanged(this.treeViewId, treeItemHandle, checked);
}
onNodeSelected(items: ITreeComponentItem[]) {
if (items) {
(<ExtHostModelViewTreeViewsShape>this._proxy).$onNodeSelected(this.treeViewId, items.map(i => i.handle));
}
}
refresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeComponentItem }) {
}
}

View File

@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ChangeDetectorRef } from '@angular/core';
import { Registry } from 'vs/platform/registry/common/platform';
import * as nls from 'vs/nls';
import * as azdata from 'azdata';
import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelView, IModelViewEventArgs } from 'sql/platform/model/common/modelViewService';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { AngularDisposable } from 'sql/base/node/lifecycle';
import { ModelStore } from 'sql/workbench/electron-browser/modelComponents/modelStore';
import { Event, Emitter } from 'vs/base/common/event';
const componentRegistry = <IComponentRegistry>Registry.as(Extensions.ComponentContribution);
/**
* Provides common logic required for any implementation that hooks to a model provided by
* the extension host
*/
export abstract class ViewBase extends AngularDisposable implements IModelView {
protected readonly modelStore: IModelStore;
protected rootDescriptor: IComponentDescriptor;
protected _onDestroy = new Emitter<void>();
public readonly onDestroy = this._onDestroy.event;
constructor(protected changeRef: ChangeDetectorRef) {
super();
this.modelStore = new ModelStore();
}
// Properties needed by the model view code
abstract id: string;
abstract connection: azdata.connection.Connection;
abstract serverInfo: azdata.ServerInfo;
private _onEventEmitter = new Emitter<IModelViewEventArgs>();
initializeModel(rootComponent: IComponentShape, validationCallback: (componentId: string) => Thenable<boolean>): void {
let descriptor = this.defineComponent(rootComponent);
this.rootDescriptor = descriptor;
this.modelStore.registerValidationCallback(validationCallback);
// Kick off the build by detecting changes to the model
this.changeRef.detectChanges();
}
private defineComponent(component: IComponentShape): IComponentDescriptor {
let existingDescriptor = this.modelStore.getComponentDescriptor(component.id);
if (existingDescriptor) {
return existingDescriptor;
}
let typeId = componentRegistry.getIdForTypeMapping(component.type);
if (!typeId) {
// failure case
throw new Error(nls.localize('componentTypeNotRegistered', "Could not find component for type {0}", ModelComponentTypes[component.type]));
}
let descriptor = this.modelStore.createComponentDescriptor(typeId, component.id);
this.setProperties(component.id, component.properties);
this.setLayout(component.id, component.layout);
this.registerEvent(component.id);
if (component.itemConfigs) {
for (let item of component.itemConfigs) {
this.addToContainer(component.id, item);
}
}
return descriptor;
}
private removeComponent(component: IComponentShape): void {
if (component.itemConfigs) {
for (let item of component.itemConfigs) {
this.removeFromContainer(component.id, item);
}
}
}
clearContainer(componentId: string): void {
this.queueAction(componentId, (component) => component.clearContainer());
}
addToContainer(containerId: string, itemConfig: IItemConfig, index?: number): void {
// Do not return the promise as this should be non-blocking
this.queueAction(containerId, (component) => {
let childDescriptor = this.defineComponent(itemConfig.componentShape);
component.addToContainer(childDescriptor, itemConfig.config, index);
});
}
removeFromContainer(containerId: string, itemConfig: IItemConfig): void {
let childDescriptor = this.modelStore.getComponentDescriptor(itemConfig.componentShape.id);
this.queueAction(containerId, (component) => {
component.removeFromContainer(childDescriptor);
this.removeComponent(itemConfig.componentShape);
});
}
setLayout(componentId: string, layout: any): void {
if (!layout) {
return;
}
this.queueAction(componentId, (component) => component.setLayout(layout));
}
setProperties(componentId: string, properties: { [key: string]: any; }): void {
if (!properties) {
return;
}
this.queueAction(componentId, (component) => component.setProperties(properties));
}
refreshDataProvider(componentId: string, item: any): void {
this.queueAction(componentId, (component) => component.refreshDataProvider(item));
}
private queueAction<T>(componentId: string, action: (component: IComponent) => T): void {
this.modelStore.eventuallyRunOnComponent(componentId, action).catch(err => {
// TODO add error handling
});
}
registerEvent(componentId: string) {
this.queueAction(componentId, (component) => {
this._register(component.registerEventHandler(e => {
let modelViewEvent: IModelViewEventArgs = Object.assign({
componentId: componentId,
isRootComponent: componentId === this.rootDescriptor.id
}, e);
this._onEventEmitter.fire(modelViewEvent);
}));
});
}
public get onEvent(): Event<IModelViewEventArgs> {
return this._onEventEmitter.event;
}
public validate(componentId: string): Thenable<boolean> {
return new Promise(resolve => this.modelStore.eventuallyRunOnComponent(componentId, component => resolve(component.validate())));
}
public setDataProvider(handle: number, componentId: string, context: any): any {
return this.queueAction(componentId, (component) => component.setDataProvider(handle, componentId, context));
}
}

View File

@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/webview';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy
} from '@angular/core';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
import { WebviewContentOptions } from 'vs/workbench/contrib/webview/common/webview';
function reviveWebviewOptions(options: vscode.WebviewOptions): vscode.WebviewOptions {
return {
...options,
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(URI.revive) : undefined
};
}
@Component({
template: '',
selector: 'modelview-webview-component'
})
export default class WebViewComponent extends ComponentBase implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto'];
private _webview: WebviewElement;
private _renderedHtml: string;
private _extensionLocationUri: URI;
protected contextKey: IContextKey<boolean>;
protected findInputFocusContextKey: IContextKey<boolean>;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(IOpenerService) private readonly _openerService: IOpenerService,
@Inject(IWorkspaceContextService) private readonly _contextService: IWorkspaceContextService,
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
this._createWebview();
this._register(addDisposableListener(window, EventType.RESIZE, e => {
this.layout();
}));
}
private _createWebview(): void {
this._webview = this.instantiationService.createInstance(WebviewElement,
{
allowSvgs: true
},
{
allowScripts: true
});
this._webview.mountTo(this._el.nativeElement);
this._register(this._webview.onDidClickLink(link => this.onDidClickLink(link)));
this._register(this._webview.onMessage(e => {
this.fireEvent({
eventType: ComponentEventType.onMessage,
args: e
});
}));
this.setHtml();
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// Webview Functions
private setHtml(): void {
if (this._webview && this.html) {
this._renderedHtml = this.html;
this._webview.contents = this._renderedHtml;
this._webview.layout();
}
}
private sendMessage(): void {
if (this._webview && this.message) {
this._webview.sendMessage(this.message);
}
}
private onDidClickLink(link: URI): any {
if (!link) {
return;
}
if (WebViewComponent.standardSupportedLinkSchemes.indexOf(link.scheme) >= 0 || this.enableCommandUris && link.scheme === 'command') {
this._openerService.open(link);
}
}
private get enableCommandUris(): boolean {
if (this.options && this.options.enableCommandUris) {
return true;
}
return false;
}
/// IComponent implementation
public layout(): void {
let element = <HTMLElement>this._el.nativeElement;
element.style.position = this.position;
this._webview.layout();
}
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
if (this.options) {
this._webview.options = this.getExtendedOptions();
}
if (this.html !== this._renderedHtml) {
this.setHtml();
}
if (this.extensionLocation) {
this._extensionLocationUri = URI.revive(this.extensionLocation);
}
this.sendMessage();
}
// CSS-bound properties
public get message(): any {
return this.getPropertyOrDefault<azdata.WebViewProperties, any>((props) => props.message, undefined);
}
public set message(newValue: any) {
this.setPropertyFromUI<azdata.WebViewProperties, any>((properties, message) => { properties.message = message; }, newValue);
}
public get html(): string {
return this.getPropertyOrDefault<azdata.WebViewProperties, string>((props) => props.html, undefined);
}
public set html(newValue: string) {
this.setPropertyFromUI<azdata.WebViewProperties, string>((properties, html) => { properties.html = html; }, newValue);
}
public get options(): vscode.WebviewOptions {
return this.getPropertyOrDefault<azdata.WebViewProperties, vscode.WebviewOptions>((props) => props.options, undefined);
}
public get extensionLocation(): UriComponents {
return this.getPropertyOrDefault<azdata.WebViewProperties, UriComponents>((props) => props.extensionLocation, undefined);
}
private get extensionLocationUri(): URI {
if (!this._extensionLocationUri && this.extensionLocation) {
this._extensionLocationUri = URI.revive(this.extensionLocation);
}
return this._extensionLocationUri;
}
private getExtendedOptions(): WebviewContentOptions {
let options = this.options || { enableScripts: true };
options = reviveWebviewOptions(options);
return {
allowScripts: options.enableScripts,
localResourceRoots: options!.localResourceRoots || this.getDefaultLocalResourceRoots()
};
}
private getDefaultLocalResourceRoots(): URI[] {
const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri);
if (this.extensionLocationUri) {
rootPaths.push(this.extensionLocationUri);
}
return rootPaths;
}
}