mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 01:25:36 -05:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 : {};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 : {};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
102
src/sql/workbench/electron-browser/modelComponents/interfaces.ts
Normal file
102
src/sql/workbench/electron-browser/modelComponents/interfaces.ts
Normal 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>;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
*/
|
||||
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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 ;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)]);
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 : {};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 }) {
|
||||
}
|
||||
}
|
||||
148
src/sql/workbench/electron-browser/modelComponents/viewBase.ts
Normal file
148
src/sql/workbench/electron-browser/modelComponents/viewBase.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user