mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-18 02:51:36 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c2d79e9cf | ||
|
|
cd140b5527 | ||
|
|
a0456bf4f7 | ||
|
|
55e3947cf7 | ||
|
|
db5156e4cd | ||
|
|
eece0677a7 | ||
|
|
24e8c20511 | ||
|
|
886717d330 | ||
|
|
26b27a616a | ||
|
|
0c663e5555 | ||
|
|
0f087915f6 | ||
|
|
a78fa9c0f2 | ||
|
|
b1752ea635 | ||
|
|
ec150917c2 | ||
|
|
7a9a69c439 | ||
|
|
9e9862c6f0 | ||
|
|
7b76d929cd | ||
|
|
1811dfa423 |
2
.yarnrc
2
.yarnrc
@@ -1,3 +1,3 @@
|
||||
disturl "https://atom.io/download/electron"
|
||||
target "1.7.11"
|
||||
target "1.7.12"
|
||||
runtime "electron"
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
||||
# Change Log
|
||||
|
||||
## Version 0.28.6
|
||||
* Release date: April 25, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
## What's new in this version
|
||||
The April Public Preview release contains some of the following highlights.
|
||||
|
||||
* Improvements to SQL Agent *Preview* extension
|
||||
* Accessibility improvements for keyboard navigation, screen reader support and high-contrast mode.
|
||||
* Improved large and protected file support for saving Admin protected and >256M files within SQL Ops Studio
|
||||
* Integrated Terminal splitting to work with multiple open terminals at once
|
||||
* Reduced installation on-disk file count foot print for faster installs and startup times
|
||||
* Improvements to Server Reports extension
|
||||
* Continue to fix GitHub issues
|
||||
|
||||
## Version 0.27.3
|
||||
* Release date: March 28, 2017
|
||||
* Release status: Public Preview
|
||||
|
||||
20
README.md
20
README.md
@@ -8,12 +8,12 @@ SQL Operations Studio is a data management tool that enables you to work with SQ
|
||||
|
||||
Platform | Link
|
||||
-- | --
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=870837
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=870838
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=870839
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=870840
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=870842
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=870841
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=872717
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=872718
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=872719
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=872720
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=872722
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=872721
|
||||
|
||||
Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions.
|
||||
|
||||
@@ -21,14 +21,6 @@ Try out the latest insiders build from `master` at https://github.com/Microsoft/
|
||||
|
||||
See the [change log](https://github.com/Microsoft/sqlopsstudio/blob/master/CHANGELOG.md) for additional details of what's in this release.
|
||||
|
||||
**Design Discussions**
|
||||
|
||||
The SQL Operations Studio team would like to incorporate community feedback earlier in the development process. To facilitate this, we'd like to share our designs while features are actively being built.
|
||||
|
||||
We're currently collecting input on the **SQL Agent** experience and enhancements to the Manage Dashboard that we're calling **"Command Center"**. We'll add additional design feedback requests below as we start work in new feature areas. Please leave comments on these issues to help us understand your requirements and shape feature development.
|
||||
|
||||
* [#750 Seeking community feedback on SQL Agent UX prototype](https://github.com/Microsoft/sqlopsstudio/issues/750)
|
||||
|
||||
**Feature Highlights**
|
||||
|
||||
- Cross-Platform DB management for Windows, macOS and Linux with simple XCopy deployment
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.4.0-alpha.23",
|
||||
"version": "1.4.0-alpha.25",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.1.zip",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqlops",
|
||||
"version": "0.28.6",
|
||||
"version": "0.29.1",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Package: @@NAME@@
|
||||
Version: @@VERSION@@
|
||||
Section: devel
|
||||
Depends: libnotify4, libnss3, gnupg, apt, libxkbfile1, libgconf-2-4, libsecret-1-0
|
||||
Depends: libnotify4, libnss3, gnupg, apt, libxkbfile1, libgconf-2-4, libsecret-1-0, libunwind8
|
||||
Priority: optional
|
||||
Architecture: @@ARCHITECTURE@@
|
||||
Maintainer: Microsoft Corporation
|
||||
|
||||
4133
samples/extensionSamples/package-lock.json
generated
4133
samples/extensionSamples/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
75
src/sql/base/browser/ui/scrollable/scrollable.directive.ts
Normal file
75
src/sql/base/browser/ui/scrollable/scrollable.directive.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Directive, Inject, forwardRef, ElementRef } from '@angular/core';
|
||||
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { getContentHeight, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
@Directive({
|
||||
selector: '[scrollable]'
|
||||
})
|
||||
export class ScrollableDirective extends AngularDisposable {
|
||||
private scrollableElement: ScrollableElement;
|
||||
private parent: HTMLElement;
|
||||
private scrolled: HTMLElement;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
|
||||
) {
|
||||
super();
|
||||
this.scrolled = this._el.nativeElement as HTMLElement;
|
||||
this.parent = this.scrolled.parentElement;
|
||||
this.parent.removeChild(this.scrolled);
|
||||
|
||||
this.scrolled.style.position = 'relative';
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.scrolled, {
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
useShadows: false
|
||||
});
|
||||
|
||||
this.scrollableElement.onScroll(e => {
|
||||
this.scrolled.style.bottom = e.scrollTop + 'px';
|
||||
});
|
||||
|
||||
this.parent.appendChild(this.scrollableElement.getDomNode());
|
||||
const initialHeight = getContentHeight(this.scrolled);
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this.scrolled),
|
||||
height: getContentHeight(this.parent)
|
||||
});
|
||||
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
this.resetScrollDimensions();
|
||||
}));
|
||||
|
||||
// unforunately because of angular rendering behavior we need to do a double check to make sure nothing changed after this point
|
||||
setTimeout(() => {
|
||||
let currentheight = getContentHeight(this.scrolled);
|
||||
if (initialHeight !== currentheight) {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: currentheight,
|
||||
height: getContentHeight(this.parent)
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
|
||||
}
|
||||
|
||||
private resetScrollDimensions() {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this.scrolled),
|
||||
height: getContentHeight(this.parent)
|
||||
});
|
||||
}
|
||||
|
||||
public layout() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -88,19 +88,6 @@ export function parseNumAsTimeString(value: number): string {
|
||||
return tempVal > 0 ? rs + '.' + mss : rs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts <, >, &, ", ', and any characters that are outside \u00A0 to numeric HTML entity values
|
||||
* like {
|
||||
* (Adapted from http://stackoverflow.com/a/18750001)
|
||||
* @param str String to convert
|
||||
* @return String with characters replaced.
|
||||
*/
|
||||
export function htmlEntities(str: string): string {
|
||||
return typeof (str) === 'string'
|
||||
? str.replace(/[\u00A0-\u9999<>\&"']/gim, (i) => { return `&#${i.charCodeAt(0)};`; })
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): string {
|
||||
let prefix = purpose ? uriPrefixes[purpose] : uriPrefixes.default;
|
||||
let uri = generateUriWithPrefix(connection, prefix);
|
||||
|
||||
@@ -5,33 +5,37 @@
|
||||
|
||||
import 'vs/css!./dashboardHomeContainer';
|
||||
|
||||
import { Component, forwardRef, Input, ChangeDetectorRef, Inject, ViewChild } from '@angular/core';
|
||||
import { Component, forwardRef, Input, ChangeDetectorRef, Inject, ViewChild, ContentChild } from '@angular/core';
|
||||
|
||||
import { DashboardWidgetContainer } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.component';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { AngularEventType } from '../../../services/angularEventing/angularEventingService';
|
||||
import { AngularEventType } from 'sql/services/angularEventing/angularEventingService';
|
||||
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-home-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardHomeContainer) }],
|
||||
template: `
|
||||
<div class="fullsize" style="display: flex; flex-direction: column">
|
||||
<dashboard-widget-wrapper #propertiesClass *ngIf="properties" [collapsable]="true" [_config]="properties"
|
||||
style="padding-left: 10px; padding-right: 10px; display: block; flex: 0" [style.height.px]="_propertiesClass?.collapsed ? '30' : '90'">
|
||||
</dashboard-widget-wrapper>
|
||||
<widget-content style="flex: 1" [widgets]="widgets" [originalConfig]="tab.originalConfig" [context]="tab.context">
|
||||
</widget-content>
|
||||
<div scrollable>
|
||||
<dashboard-widget-wrapper #propertiesClass *ngIf="properties" [collapsable]="true" [_config]="properties"
|
||||
style="padding-left: 10px; padding-right: 10px; display: block; flex: 0" [style.height.px]="_propertiesClass?.collapsed ? '30' : '90'">
|
||||
</dashboard-widget-wrapper>
|
||||
<widget-content style="flex: 1" [scrollContent]="false" [widgets]="widgets" [originalConfig]="tab.originalConfig" [context]="tab.context">
|
||||
</widget-content>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class DashboardHomeContainer extends DashboardWidgetContainer {
|
||||
@Input() private properties: WidgetConfig;
|
||||
@ViewChild('propertiesClass') private _propertiesClass: DashboardWidgetWrapper;
|
||||
@ContentChild(ScrollableDirective) private _scrollable;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
|
||||
@@ -56,4 +60,9 @@ export class DashboardHomeContainer extends DashboardWidgetContainer {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public layout() {
|
||||
super.layout();
|
||||
this._scrollable.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ export class WidgetContent extends AngularDisposable implements AfterViewInit {
|
||||
@Input() private widgets: WidgetConfig[];
|
||||
@Input() private originalConfig: WidgetConfig[];
|
||||
@Input() private context: string;
|
||||
@Input() private scrollContent = true;
|
||||
|
||||
private _scrollableElement: ScrollableElement;
|
||||
|
||||
@@ -123,41 +124,43 @@ export class WidgetContent extends AngularDisposable implements AfterViewInit {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
let container = this._scrollContainer.nativeElement as HTMLElement;
|
||||
let scrollable = this._scrollable.nativeElement as HTMLElement;
|
||||
container.removeChild(scrollable);
|
||||
if (this.scrollContent) {
|
||||
let container = this._scrollContainer.nativeElement as HTMLElement;
|
||||
let scrollable = this._scrollable.nativeElement as HTMLElement;
|
||||
container.removeChild(scrollable);
|
||||
|
||||
this._scrollableElement = new ScrollableElement(scrollable, {
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
useShadows: false
|
||||
});
|
||||
this._scrollableElement = new ScrollableElement(scrollable, {
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
useShadows: false
|
||||
});
|
||||
|
||||
this._scrollableElement.onScroll(e => {
|
||||
scrollable.style.bottom = e.scrollTop + 'px';
|
||||
});
|
||||
this._scrollableElement.onScroll(e => {
|
||||
scrollable.style.bottom = e.scrollTop + 'px';
|
||||
});
|
||||
|
||||
container.appendChild(this._scrollableElement.getDomNode());
|
||||
let initalHeight = getContentHeight(scrollable);
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(scrollable),
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
container.appendChild(this._scrollableElement.getDomNode());
|
||||
let initalHeight = getContentHeight(scrollable);
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(scrollable),
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
this.resetScrollDimensions();
|
||||
}));
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
this.resetScrollDimensions();
|
||||
}));
|
||||
|
||||
// unforunately because of angular rendering behavior we need to do a double check to make sure nothing changed after this point
|
||||
setTimeout(() => {
|
||||
let currentheight = getContentHeight(scrollable);
|
||||
if (initalHeight !== currentheight) {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: currentheight,
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
// unforunately because of angular rendering behavior we need to do a double check to make sure nothing changed after this point
|
||||
setTimeout(() => {
|
||||
let currentheight = getContentHeight(scrollable);
|
||||
if (initalHeight !== currentheight) {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: currentheight,
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
public layout() {
|
||||
@@ -167,7 +170,9 @@ export class WidgetContent extends AngularDisposable implements AfterViewInit {
|
||||
});
|
||||
}
|
||||
this._grid.triggerResize();
|
||||
this.resetScrollDimensions();
|
||||
if (this.scrollContent) {
|
||||
this.resetScrollDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
private resetScrollDimensions() {
|
||||
|
||||
@@ -54,9 +54,10 @@ import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.comp
|
||||
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
|
||||
|
||||
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
|
||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper];
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
|
||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper,
|
||||
ScrollableDirective];
|
||||
|
||||
/* Panel */
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
@@ -74,6 +75,7 @@ import { TasksWidget } from 'sql/parts/dashboard/widgets/tasks/tasksWidget.compo
|
||||
import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWidget.component';
|
||||
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
|
||||
import { JobStepsViewComponent } from '../jobManagement/views/jobStepsView.component';
|
||||
import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive';
|
||||
|
||||
let widgetComponents = [
|
||||
PropertiesWidgetComponent,
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
import * as Strings from 'vs/base/common/strings';
|
||||
|
||||
export class DBCellValue {
|
||||
displayValue: string;
|
||||
isNull: boolean;
|
||||
|
||||
public static isDBCellValue(object: any): boolean {
|
||||
return (object !== undefined && object.displayValue !== undefined && object.isNull !== undefined);
|
||||
return (object !== undefined && object.displayValue !== undefined && object.isNull !== undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function hyperLinkFormatter(row: number, cell: any, value: any, columnDef
|
||||
valueToDisplay = 'NULL';
|
||||
if (!value.isNull) {
|
||||
cellClasses += ' xmlLink';
|
||||
valueToDisplay = Utils.htmlEntities(value.displayValue);
|
||||
valueToDisplay = Strings.escape(value.displayValue);
|
||||
return `<a class="${cellClasses}" href="#" >${valueToDisplay}</a>`;
|
||||
} else {
|
||||
cellClasses += ' missing-value';
|
||||
@@ -44,13 +44,12 @@ export function textFormatter(row: number, cell: any, value: any, columnDef: any
|
||||
if (DBCellValue.isDBCellValue(value)) {
|
||||
valueToDisplay = 'NULL';
|
||||
if (!value.isNull) {
|
||||
valueToDisplay = Utils.htmlEntities(value.displayValue.replace(/(\r\n|\n|\r)/g, ' '));
|
||||
valueToDisplay = Strings.escape(value.displayValue.replace(/(\r\n|\n|\r)/g, ' '));
|
||||
} else {
|
||||
cellClasses += ' missing-value';
|
||||
}
|
||||
} else if (typeof value === 'string'){
|
||||
valueToDisplay = value;
|
||||
|
||||
} else if (typeof value === 'string') {
|
||||
valueToDisplay = Strings.escape(value);
|
||||
}
|
||||
|
||||
return `<span title="${valueToDisplay}" class="${cellClasses}">${valueToDisplay}</span>`;
|
||||
|
||||
@@ -9,6 +9,9 @@ import * as nls from 'vs/nls';
|
||||
|
||||
export class AgentJobUtilities {
|
||||
|
||||
public static startIconClass: string = 'icon-start';
|
||||
public static stopIconClass: string = 'icon-stop';
|
||||
|
||||
public static convertToStatusString(status: number): string {
|
||||
switch(status) {
|
||||
case(0): return nls.localize('agentUtilities.failed','Failed');
|
||||
@@ -51,4 +54,41 @@ export class AgentJobUtilities {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
public static setRunnable(icon: HTMLElement, index: number) {
|
||||
if (icon.className.includes('non-runnable')) {
|
||||
icon.className = icon.className.slice(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
public static getActionIconClassName(startIcon: HTMLElement, stopIcon: HTMLElement, executionStatus: number) {
|
||||
this.setRunnable(startIcon, AgentJobUtilities.startIconClass.length);
|
||||
this.setRunnable(stopIcon, AgentJobUtilities.stopIconClass.length);
|
||||
switch (executionStatus) {
|
||||
case(1): // executing
|
||||
startIcon.className += ' non-runnable';
|
||||
return;
|
||||
case(2): // Waiting for thread
|
||||
startIcon.className += ' non-runnable';
|
||||
return;
|
||||
case(3): // Between retries
|
||||
startIcon.className += ' non-runnable';
|
||||
return;
|
||||
case(4): //Idle
|
||||
stopIcon.className += ' non-runnable';
|
||||
return;
|
||||
case(5): // Suspended
|
||||
stopIcon.className += ' non-runnable';
|
||||
return;
|
||||
case(6): //obsolete
|
||||
startIcon.className += ' non-runnable';
|
||||
stopIcon.className += ' non-runnable';
|
||||
return;
|
||||
case(7): //Performing Completion Actions
|
||||
startIcon.className += ' non-runnable';
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,9 @@ jobhistory-component {
|
||||
}
|
||||
|
||||
.vs-dark .job-heading-container {
|
||||
height: 32px;
|
||||
height: 49px;
|
||||
border-bottom: 3px solid #444444;
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid {
|
||||
@@ -182,4 +183,11 @@ jobsview-component .jobview-grid > .monaco-table .slick-viewport > .grid-canvas
|
||||
|
||||
.vs-dark .jobview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted white;
|
||||
}
|
||||
|
||||
.job-heading-container > .icon.in-progress {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
padding-top: 16px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
@@ -4,8 +4,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
|
||||
<div class="jobhistory-heading-container">
|
||||
<h1 class="job-heading">Jobs | {{this._agentJobInfo?.name}} </h1>
|
||||
<div class="icon in-progress" *ngIf="showProgressWheel()"></div>
|
||||
</div>
|
||||
|
||||
<!-- Back -->
|
||||
<div class="all-jobs">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./jobHistory';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
import { OnInit, OnChanges, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core';
|
||||
import { AgentJobHistoryInfo, AgentJobInfo } from 'sqlops';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -60,6 +61,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
private _jobCacheObject: JobCacheObject;
|
||||
private _notificationService: INotificationService;
|
||||
private _agentJobInfo: AgentJobInfo;
|
||||
private _noJobsAvailable: boolean = false;
|
||||
|
||||
constructor(
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private bootstrapService: IBootstrapService,
|
||||
@@ -131,6 +133,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
if (!this.agentJobInfo) {
|
||||
this.agentJobInfo = this._agentJobInfo;
|
||||
this.setActions();
|
||||
}
|
||||
if (this._isVisible === false && this._tableContainer.nativeElement.offsetParent !== null) {
|
||||
this._isVisible = true;
|
||||
@@ -145,7 +148,10 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
} else if (jobHistories && jobHistories.length === 0 ){
|
||||
this._showPreviousRuns = false;
|
||||
this._showSteps = false;
|
||||
this._noJobsAvailable = true;
|
||||
this._cd.detectChanges();
|
||||
} else {
|
||||
this.loadHistory();
|
||||
}
|
||||
this._jobCacheObject.prevJobID = this._agentViewComponent.jobId;
|
||||
} else if (this._isVisible === true && this._agentViewComponent.refresh) {
|
||||
@@ -273,6 +279,16 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
return time.replace('T', ' ');
|
||||
}
|
||||
|
||||
private showProgressWheel(): boolean {
|
||||
return this._showPreviousRuns !== true && this._noJobsAvailable === false;
|
||||
}
|
||||
|
||||
private setActions(): void {
|
||||
let startIcon: HTMLElement = $('.icon-start').get(0);
|
||||
let stopIcon: HTMLElement = $('.icon-stop').get(0);
|
||||
AgentJobUtilities.getActionIconClassName(startIcon, stopIcon, this.agentJobInfo.currentExecutionStatus);
|
||||
}
|
||||
|
||||
public get showSteps(): boolean {
|
||||
return this._showSteps;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ ul.action-buttons li {
|
||||
padding-right: 25px;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overview-container .overview-tab .resultsViewCollapsible {
|
||||
@@ -134,6 +133,7 @@ input#accordion:checked ~ .accordion-content {
|
||||
width: 20px;
|
||||
background-image: url('../common/media/start.svg');
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vs ul.action-buttons .icon-stop,
|
||||
@@ -144,6 +144,17 @@ input#accordion:checked ~ .accordion-content {
|
||||
background-repeat: no-repeat;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul.action-buttons div.icon-start.non-runnable {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
ul.action-buttons div.icon-stop.non-runnable {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.accordion-content #col1,
|
||||
@@ -240,4 +251,15 @@ table.step-list tr.step-row td {
|
||||
|
||||
jobhistory-component .history-details .step-table.prev-run-list .monaco-scrollable-element {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
jobhistory-component .jobhistory-heading-container {
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-top: 16px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
<div class="job-heading-container">
|
||||
<h1 class="job-heading" *ngIf="_isCloud === false">Jobs</h1>
|
||||
<h1 class="job-heading" *ngIf="_isCloud === true">No Jobs Available</h1>
|
||||
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
|
||||
</div>
|
||||
|
||||
<div #jobsgrid class="jobview-grid"></div>
|
||||
@@ -9,6 +9,7 @@ import 'vs/css!sql/parts/grid/media/styles';
|
||||
import 'vs/css!sql/parts/grid/media/slick.grid';
|
||||
import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
import 'vs/css!../common/media/jobs';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
@@ -72,6 +73,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
public jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = Object.create(null);
|
||||
private _serverName: string;
|
||||
private _isCloud: boolean;
|
||||
private _showProgressWheel: boolean;
|
||||
|
||||
constructor(
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private bootstrapService: IBootstrapService,
|
||||
@@ -99,18 +101,22 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
this.isVisible = true;
|
||||
if (!this.isInitialized) {
|
||||
if (this._jobCacheObject.serverName === this._serverName && this._jobCacheObject.jobs.length > 0) {
|
||||
this._showProgressWheel = true;
|
||||
this.jobs = this._jobCacheObject.jobs;
|
||||
this.onFirstVisible(true);
|
||||
this.isInitialized = true;
|
||||
} else {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this.isInitialized = true;
|
||||
}
|
||||
}
|
||||
} else if (this.isVisible === true && this._agentViewComponent.refresh === true) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this._agentViewComponent.refresh = false;
|
||||
} else if (this.isVisible === true && this._agentViewComponent.refresh === false) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(true);
|
||||
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
|
||||
this.isVisible = false;
|
||||
@@ -219,6 +225,8 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
let currentTarget = e.currentTarget;
|
||||
currentTarget.title = currentTarget.innerText;
|
||||
});
|
||||
this._showProgressWheel = false;
|
||||
this._cd.detectChanges();
|
||||
this.loadJobHistories();
|
||||
}
|
||||
|
||||
|
||||
97
src/sql/parts/modelComponents/button.component.ts
Normal file
97
src/sql/parts/modelComponents/button.component.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
|
||||
} from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { attachButtonStyler } from 'sql/common/theme/styler';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'button',
|
||||
template: `
|
||||
<div #input style="width: 100%"></div>
|
||||
`
|
||||
})
|
||||
export default class ButtonComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
private _button: Button;
|
||||
|
||||
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
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._commonService.themeService, {
|
||||
buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND
|
||||
}));
|
||||
this._register(this._button.onDidClick(e => {
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: e
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public layout(): void {
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
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.label = this.label;
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
|
||||
private get label(): string {
|
||||
return this.getPropertyOrDefault<sqlops.ButtonProperties, string>((props) => props.label, '');
|
||||
}
|
||||
|
||||
private set label(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.ButtonProperties, string>(this.setValueProperties, newValue);
|
||||
}
|
||||
|
||||
private setValueProperties(properties: sqlops.ButtonProperties, label: string): void {
|
||||
properties.label = label;
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
return types.isUndefinedOrNull(property) ? defaultVal : property;
|
||||
}
|
||||
|
||||
protected setProperty<TPropertyBag, TValue>(propertySetter: (TPropertyBag, TValue) => void, value: TValue) {
|
||||
protected setPropertyFromUI<TPropertyBag, TValue>(propertySetter: (TPropertyBag, TValue) => void, value: TValue) {
|
||||
propertySetter(this.getProperties<TPropertyBag>(), value);
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.PropertiesChanged,
|
||||
@@ -86,6 +86,12 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
public get onEvent(): Event<IComponentEventArgs> {
|
||||
return this._onEventEmitter.event;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
let properties = this.getProperties();
|
||||
let title = properties['title'];
|
||||
return title ? <string>title : '';
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ContainerBase<T> extends ComponentBase {
|
||||
|
||||
@@ -4,16 +4,28 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import FlexContainer from './flexContainer.component';
|
||||
import FormContainer from './formContainer.component';
|
||||
import CardComponent from './card.component';
|
||||
import InputBoxComponent from './inputbox.component';
|
||||
import DropDownComponent from './dropdown.component';
|
||||
import ButtonComponent from './button.component';
|
||||
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export const FLEX_CONTAINER = 'flex-container';
|
||||
registerComponentType(FLEX_CONTAINER, ModelComponentTypes.FlexContainer, FlexContainer);
|
||||
|
||||
export const FORM_CONTAINER = 'form-container';
|
||||
registerComponentType(FORM_CONTAINER, ModelComponentTypes.Form, FormContainer);
|
||||
|
||||
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 BUTTON_COMPONENT = 'button-component';
|
||||
registerComponentType(BUTTON_COMPONENT, ModelComponentTypes.Button, ButtonComponent);
|
||||
|
||||
117
src/sql/parts/modelComponents/dropdown.component.ts
Normal file
117
src/sql/parts/modelComponents/dropdown.component.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
|
||||
} from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
|
||||
@Component({
|
||||
selector: 'inputBox',
|
||||
template: `
|
||||
<div #input style="width: 100%"></div>
|
||||
`
|
||||
})
|
||||
export default class DropDownComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
private _dropdown: Dropdown;
|
||||
|
||||
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this._inputContainer) {
|
||||
let dropdownOptions: IDropdownOptions = {
|
||||
values: [],
|
||||
strictSelection: false,
|
||||
placeholder: '',
|
||||
maxHeight: 125,
|
||||
ariaLabel: ''
|
||||
};
|
||||
|
||||
this._dropdown = new Dropdown(this._inputContainer.nativeElement, this._commonService.contextViewService, this._commonService.themeService,
|
||||
dropdownOptions);
|
||||
|
||||
this._register(this._dropdown);
|
||||
this._register(attachEditableDropdownStyler(this._dropdown, this._commonService.themeService));
|
||||
this._register(this._dropdown.onValueChange(e => {
|
||||
this.value = this._dropdown.value;
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidChange,
|
||||
args: e
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public layout(): void {
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
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._dropdown.values = this.values ? this.values : [];
|
||||
if (this.value) {
|
||||
this._dropdown.value = this.value;
|
||||
}
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
|
||||
private get value(): string {
|
||||
return this.getPropertyOrDefault<sqlops.DropDownProperties, string>((props) => props.value, '');
|
||||
}
|
||||
|
||||
private set value(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.DropDownProperties, string>(this.setValueProperties, newValue);
|
||||
}
|
||||
|
||||
private get values(): string[] {
|
||||
return this.getPropertyOrDefault<sqlops.DropDownProperties, string[]>((props) => props.values, undefined);
|
||||
}
|
||||
|
||||
private set values(newValue: string[]) {
|
||||
this.setPropertyFromUI<sqlops.DropDownProperties, string[]>(this.setValuesProperties, newValue);
|
||||
}
|
||||
|
||||
private setValueProperties(properties: sqlops.DropDownProperties, value: string): void {
|
||||
properties.value = value;
|
||||
}
|
||||
|
||||
private setValuesProperties(properties: sqlops.DropDownProperties, values: string[]): void {
|
||||
properties.values = values;
|
||||
}
|
||||
}
|
||||
128
src/sql/parts/modelComponents/formContainer.component.ts
Normal file
128
src/sql/parts/modelComponents/formContainer.component.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./formLayout';
|
||||
|
||||
import {
|
||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
|
||||
} from '@angular/core';
|
||||
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { FormLayout, FormItemLayout } from 'sqlops';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { ContainerBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
|
||||
export interface TitledFormItemLayout {
|
||||
title: string;
|
||||
actions?: string[];
|
||||
isFormComponent: Boolean;
|
||||
}
|
||||
class FormItem {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: TitledFormItemLayout) { }
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div #container *ngIf="items" class="form-table"
|
||||
[style.alignItems]="alignItems" [style.alignContent]="alignContent">
|
||||
<div *ngFor="let item of items" class="form-row">
|
||||
<ng-container *ngIf="isFormComponent(item)">
|
||||
<div class="form-cell">{{getItemTitle(item)}}</div>
|
||||
<div class="form-cell">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
<div *ngIf="itemHasActions(item)" class="form-cell">
|
||||
<div *ngFor="let actionItem of getActionComponents(item)" >
|
||||
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export default class FormContainer extends ContainerBase<FormItemLayout> implements IComponent, OnDestroy, AfterViewInit {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
|
||||
private _alignItems: string;
|
||||
private _alignContent: string;
|
||||
|
||||
@ViewChildren(ModelComponentWrapper) private _componentWrappers: QueryList<ModelComponentWrapper>;
|
||||
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
|
||||
|
||||
constructor (
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public layout(): void {
|
||||
if (this._componentWrappers) {
|
||||
this._componentWrappers.forEach(wrapper => {
|
||||
wrapper.layout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get alignItems(): string {
|
||||
return this._alignItems;
|
||||
}
|
||||
|
||||
public get alignContent(): string {
|
||||
return this._alignContent;
|
||||
}
|
||||
|
||||
private getItemTitle(item: FormItem): string {
|
||||
let itemConfig = item.config;
|
||||
return itemConfig ? itemConfig.title : '';
|
||||
}
|
||||
|
||||
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 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: any): void {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
20
src/sql/parts/modelComponents/formLayout.css
Normal file
20
src/sql/parts/modelComponents/formLayout.css
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
.form-table {
|
||||
width:400px;
|
||||
display:table;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: table-row;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.form-cell {
|
||||
padding: 5px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.form-action {
|
||||
width: 20px;
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
import {
|
||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
|
||||
} from '@angular/core';
|
||||
|
||||
@@ -70,7 +71,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
public setLayout (layout: any): void {
|
||||
public setLayout(layout: any): void {
|
||||
// TODO allow configuring the look and feel
|
||||
this.layout();
|
||||
}
|
||||
@@ -87,7 +88,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
}
|
||||
|
||||
public set value(newValue: string) {
|
||||
this.setProperty<sqlops.InputBoxProperties, string>(this.setInputBoxProperties, newValue);
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, string>(this.setInputBoxProperties, newValue);
|
||||
}
|
||||
|
||||
private setInputBoxProperties(properties: sqlops.InputBoxProperties, value: string): void {
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface IComponent {
|
||||
addToContainer?: (componentDescriptor: IComponentDescriptor, config: any) => void;
|
||||
setLayout?: (layout: any) => void;
|
||||
setProperties?: (properties: { [key: string]: any; }) => void;
|
||||
title?: string;
|
||||
onEvent?: Event<IComponentEventArgs>;
|
||||
}
|
||||
|
||||
@@ -53,11 +54,13 @@ export interface IComponentDescriptor {
|
||||
export interface IComponentEventArgs {
|
||||
eventType: ComponentEventType;
|
||||
args: any;
|
||||
componentId?: string;
|
||||
}
|
||||
|
||||
export enum ComponentEventType {
|
||||
PropertiesChanged,
|
||||
onDidChange
|
||||
onDidChange,
|
||||
onDidClick
|
||||
}
|
||||
|
||||
export interface IModelStore {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import nls = require('vs/nls');
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
|
||||
import { IModelStore, IComponentDescriptor, IComponent, IComponentEventArgs } from './interfaces';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
@@ -18,7 +18,7 @@ import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { ModelStore } from 'sql/parts/modelComponents/modelStore';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
const componentRegistry = <IComponentRegistry> Registry.as(Extensions.ComponentContribution);
|
||||
const componentRegistry = <IComponentRegistry>Registry.as(Extensions.ComponentContribution);
|
||||
|
||||
/**
|
||||
* Provides common logic required for any implementation that hooks to a model provided by
|
||||
@@ -57,7 +57,7 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
this.setLayout(component.id, component.layout);
|
||||
this.registerEvent(component.id);
|
||||
if (component.itemConfigs) {
|
||||
for(let item of component.itemConfigs) {
|
||||
for (let item of component.itemConfigs) {
|
||||
this.addToContainer(component.id, item);
|
||||
}
|
||||
}
|
||||
@@ -66,12 +66,12 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
}
|
||||
|
||||
clearContainer(componentId: string): void {
|
||||
this.queueAction(componentId, (component) => component.clearContainer());
|
||||
this.queueAction(componentId, (component) => component.clearContainer());
|
||||
}
|
||||
|
||||
addToContainer(containerId: string, itemConfig: IItemConfig): void {
|
||||
// Do not return the promise as this should be non-blocking
|
||||
this.queueAction(containerId, (component) => {
|
||||
this.queueAction(containerId, (component) => {
|
||||
let childDescriptor = this.defineComponent(itemConfig.componentShape);
|
||||
component.addToContainer(childDescriptor, itemConfig.config);
|
||||
});
|
||||
@@ -81,14 +81,14 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
if (!layout) {
|
||||
return;
|
||||
}
|
||||
this.queueAction(componentId, (component) => component.setLayout(layout));
|
||||
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));
|
||||
this.queueAction(componentId, (component) => component.setProperties(properties));
|
||||
}
|
||||
|
||||
private queueAction<T>(componentId: string, action: (component: IComponent) => T): void {
|
||||
@@ -98,16 +98,17 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
}
|
||||
|
||||
registerEvent(componentId: string) {
|
||||
this.queueAction(componentId, (component) => {
|
||||
this.queueAction(componentId, (component) => {
|
||||
if (component.onEvent) {
|
||||
this._register(component.onEvent(e => {
|
||||
e.componentId = componentId;
|
||||
this._onEventEmitter.fire(e);
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get onEvent(): Event<any> {
|
||||
public get onEvent(): Event<IComponentEventArgs> {
|
||||
return this._onEventEmitter.event;
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,24 @@ import { Dialog } from 'sql/platform/dialog/dialogTypes';
|
||||
import { IModalOptions } from 'sql/base/browser/ui/modal/modal';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
const defaultOptions: IModalOptions = { hasBackButton: true, isWide: true };
|
||||
const defaultOptions: IModalOptions = { hasBackButton: true, isWide: false };
|
||||
|
||||
export class CustomDialogService {
|
||||
private _dialogModals = new Map<Dialog, DialogModal>();
|
||||
|
||||
constructor( @IInstantiationService private _instantiationService: IInstantiationService) { }
|
||||
|
||||
public showDialog(dialog: Dialog, options?: IModalOptions): void {
|
||||
let optionsDialog = this._instantiationService.createInstance(DialogModal, dialog, 'CustomDialog', options || defaultOptions);
|
||||
optionsDialog.render();
|
||||
optionsDialog.open();
|
||||
let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, 'CustomDialog', options || defaultOptions);
|
||||
this._dialogModals.set(dialog, dialogModal);
|
||||
dialogModal.render();
|
||||
dialogModal.open();
|
||||
}
|
||||
|
||||
public closeDialog(dialog: Dialog): void {
|
||||
let dialogModal = this._dialogModals.get(dialog);
|
||||
if (dialogModal) {
|
||||
dialogModal.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import 'vs/css!./media/dialogModal';
|
||||
import { Modal, IModalOptions } from 'sql/base/browser/ui/modal/modal';
|
||||
import { attachModalDialogStyler } from 'sql/common/theme/styler';
|
||||
import { Dialog } from 'sql/platform/dialog/dialogTypes';
|
||||
import { Dialog, DialogButton } from 'sql/platform/dialog/dialogTypes';
|
||||
import { DialogPane } from 'sql/platform/dialog/dialogPane';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
@@ -23,9 +23,6 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class DialogModal extends Modal {
|
||||
private static readonly DONE_BUTTON_LABEL = localize('dialogModalDoneButtonLabel', 'Done');
|
||||
private static readonly CANCEL_BUTTON_LABEL = localize('dialogModalCancelButtonLabel', 'Cancel');
|
||||
|
||||
private _dialogPane: DialogPane;
|
||||
|
||||
// Wizard HTML elements
|
||||
@@ -61,10 +58,34 @@ export class DialogModal extends Modal {
|
||||
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
|
||||
}
|
||||
|
||||
this._cancelButton = this.addFooterButton(DialogModal.CANCEL_BUTTON_LABEL, () => this.cancel());
|
||||
this._doneButton = this.addFooterButton(DialogModal.DONE_BUTTON_LABEL, () => this.done());
|
||||
attachButtonStyler(this._cancelButton, this._themeService);
|
||||
attachButtonStyler(this._doneButton, this._themeService);
|
||||
if (this._dialog.customButtons) {
|
||||
this._dialog.customButtons.forEach(button => {
|
||||
let buttonElement = this.addDialogButton(button);
|
||||
this.updateButtonElement(buttonElement, button);
|
||||
});
|
||||
}
|
||||
|
||||
this._cancelButton = this.addDialogButton(this._dialog.cancelButton, () => this.cancel());
|
||||
this.updateButtonElement(this._cancelButton, this._dialog.cancelButton);
|
||||
this._doneButton = this.addDialogButton(this._dialog.okButton, () => this.done());
|
||||
this.updateButtonElement(this._doneButton, this._dialog.okButton);
|
||||
}
|
||||
|
||||
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined): Button {
|
||||
let buttonElement = this.addFooterButton(button.label, onSelect);
|
||||
buttonElement.enabled = button.enabled;
|
||||
button.registerClickEvent(buttonElement.onDidClick);
|
||||
button.onUpdate(() => {
|
||||
this.updateButtonElement(buttonElement, button);
|
||||
});
|
||||
attachButtonStyler(buttonElement, this._themeService);
|
||||
return buttonElement;
|
||||
}
|
||||
|
||||
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton) {
|
||||
buttonElement.label = dialogButton.label;
|
||||
buttonElement.enabled = dialogButton.enabled;
|
||||
dialogButton.hidden ? buttonElement.element.classList.add('dialogModal-hidden') : buttonElement.element.classList.remove('dialogModal-hidden');
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
|
||||
@@ -92,4 +92,9 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
this._body.style.backgroundColor = styles.dialogBodyBackground ? styles.dialogBodyBackground.toString() : undefined;
|
||||
this._body.style.color = styles.dialogForeground ? styles.dialogForeground.toString() : undefined;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this._moduleRef.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { localize } from 'vs/nls';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class DialogTab implements sqlops.window.modelviewdialog.DialogTab {
|
||||
@@ -16,40 +17,70 @@ export class DialogTab implements sqlops.window.modelviewdialog.DialogTab {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
public updateContent(): void { }
|
||||
}
|
||||
|
||||
export class Dialog implements sqlops.window.modelviewdialog.Dialog {
|
||||
public content: string | DialogTab[];
|
||||
public okTitle: string;
|
||||
public cancelTitle: string;
|
||||
public customButtons: DialogButton[];
|
||||
private static readonly DONE_BUTTON_LABEL = localize('dialogModalDoneButtonLabel', 'Done');
|
||||
private static readonly CANCEL_BUTTON_LABEL = localize('dialogModalCancelButtonLabel', 'Cancel');
|
||||
|
||||
private _onOk: Emitter<void> = new Emitter<void>();
|
||||
public readonly onOk: Event<void> = this._onOk.event;
|
||||
private _onCancel: Emitter<void> = new Emitter<void>();
|
||||
public readonly onCancel: Event<void> = this._onCancel.event;
|
||||
public content: string | DialogTab[];
|
||||
public okButton: DialogButton = new DialogButton(Dialog.DONE_BUTTON_LABEL, true);
|
||||
public cancelButton: DialogButton = new DialogButton(Dialog.CANCEL_BUTTON_LABEL, true);
|
||||
public customButtons: DialogButton[];
|
||||
|
||||
constructor(public title: string, content?: string | DialogTab[]) {
|
||||
if (content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
public open(): void { }
|
||||
public close(): void { }
|
||||
public updateContent(): void { }
|
||||
}
|
||||
|
||||
export class DialogButton implements sqlops.window.modelviewdialog.Button {
|
||||
public label: string;
|
||||
public enabled: boolean;
|
||||
private _label: string;
|
||||
private _enabled: boolean;
|
||||
private _hidden: boolean;
|
||||
private _onClick: Emitter<void> = new Emitter<void>();
|
||||
public readonly onClick: Event<void> = this._onClick.event;
|
||||
private _onUpdate: Emitter<void> = new Emitter<void>();
|
||||
public readonly onUpdate: Event<void> = this._onUpdate.event;
|
||||
|
||||
constructor(label: string, enabled: boolean) {
|
||||
this.label = label;
|
||||
this.enabled = enabled;
|
||||
this._label = label;
|
||||
this._enabled = enabled;
|
||||
this._hidden = false;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public set label(label: string) {
|
||||
this._label = label;
|
||||
this._onUpdate.fire();
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(enabled: boolean) {
|
||||
this._enabled = enabled;
|
||||
this._onUpdate.fire();
|
||||
}
|
||||
|
||||
public get hidden(): boolean {
|
||||
return this._hidden;
|
||||
}
|
||||
|
||||
public set hidden(hidden: boolean) {
|
||||
this._hidden = hidden;
|
||||
this._onUpdate.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event that notifies the button that it has been clicked
|
||||
*/
|
||||
public registerClickEvent(clickEvent: Event<void>): void {
|
||||
clickEvent(() => this._onClick.fire());
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dialogModal-pane.dialogModal-hidden {
|
||||
.dialogModal-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
120
src/sql/sqlops.proposed.d.ts
vendored
120
src/sql/sqlops.proposed.d.ts
vendored
@@ -20,23 +20,30 @@ declare module 'sqlops' {
|
||||
flexContainer(): FlexBuilder;
|
||||
card(): ComponentBuilder<CardComponent>;
|
||||
inputBox(): ComponentBuilder<InputBoxComponent>;
|
||||
button(): ComponentBuilder<ButtonComponent>;
|
||||
dropDown(): ComponentBuilder<DropDownComponent>;
|
||||
dashboardWidget(widgetId: string): ComponentBuilder<WidgetComponent>;
|
||||
dashboardWebview(webviewId: string): ComponentBuilder<WebviewComponent>;
|
||||
formContainer(): FormBuilder;
|
||||
}
|
||||
|
||||
export interface ComponentBuilder<T extends Component> {
|
||||
component(): T;
|
||||
withProperties<U>(properties: U): ComponentBuilder<T>;
|
||||
}
|
||||
export interface ContainerBuilder<T extends Component, TLayout,TItemLayout> extends ComponentBuilder<T> {
|
||||
export interface ContainerBuilder<T extends Component, TLayout, TItemLayout> extends ComponentBuilder<T> {
|
||||
withLayout(layout: TLayout): ContainerBuilder<T, TLayout, TItemLayout>;
|
||||
withItems(components: Array<Component>, itemLayout ?: TItemLayout): ContainerBuilder<T, TLayout, TItemLayout>;
|
||||
withItems(components: Array<Component>, itemLayout?: TItemLayout): ContainerBuilder<T, TLayout, TItemLayout>;
|
||||
}
|
||||
|
||||
export interface FlexBuilder extends ContainerBuilder<FlexContainer, FlexLayout, FlexItemLayout> {
|
||||
|
||||
}
|
||||
|
||||
export interface FormBuilder extends ContainerBuilder<FormContainer, FormLayout, FormItemLayout> {
|
||||
withFormItems(components: FormComponent[], itemLayout?: FormItemLayout): ContainerBuilder<FormContainer, FormLayout, FormItemLayout>;
|
||||
}
|
||||
|
||||
export interface Component {
|
||||
readonly id: string;
|
||||
|
||||
@@ -50,10 +57,16 @@ declare module 'sqlops' {
|
||||
updateProperties(properties: { [key: string]: any }): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export interface FormComponent {
|
||||
component: Component;
|
||||
title: string;
|
||||
actions?: Component[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that contains other components
|
||||
*/
|
||||
export interface Container<TLayout,TItemLayout> extends Component {
|
||||
export interface Container<TLayout, TItemLayout> extends Component {
|
||||
/**
|
||||
* A copy of the child items array. This cannot be added to directly -
|
||||
* components must be created using the create methods instead
|
||||
@@ -70,7 +83,7 @@ declare module 'sqlops' {
|
||||
* @param itemConfigs the definitions
|
||||
* @param {*} [itemLayout] Optional layout for the child items
|
||||
*/
|
||||
addItems(itemConfigs: Array<Component>, itemLayout ?: TItemLayout): void;
|
||||
addItems(itemConfigs: Array<Component>, itemLayout?: TItemLayout): void;
|
||||
|
||||
/**
|
||||
* Creates a child component and adds it to this container.
|
||||
@@ -78,7 +91,7 @@ declare module 'sqlops' {
|
||||
* @param {Component} component the component to be added
|
||||
* @param {*} [itemLayout] Optional layout for this child item
|
||||
*/
|
||||
addItem(component: Component, itemLayout ?: TItemLayout): void;
|
||||
addItem(component: Component, itemLayout?: TItemLayout): void;
|
||||
|
||||
/**
|
||||
* Defines the layout for this container
|
||||
@@ -130,9 +143,21 @@ declare module 'sqlops' {
|
||||
flex?: string;
|
||||
}
|
||||
|
||||
export interface FormItemLayout {
|
||||
|
||||
}
|
||||
|
||||
export interface FormLayout {
|
||||
|
||||
}
|
||||
|
||||
export interface FlexContainer extends Container<FlexLayout, FlexItemLayout> {
|
||||
}
|
||||
|
||||
export interface FormContainer extends Container<FormLayout, FormItemLayout> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Describes an action to be shown in the UI, with a user-readable label
|
||||
* and a callback to execute the action
|
||||
@@ -153,16 +178,25 @@ declare module 'sqlops' {
|
||||
* Properties representing the card component, can be used
|
||||
* when using ModelBuilder to create the component
|
||||
*/
|
||||
export interface CardProperties {
|
||||
export interface CardProperties {
|
||||
label: string;
|
||||
value?: string;
|
||||
actions?: ActionDescriptor[];
|
||||
}
|
||||
|
||||
export interface InputBoxProperties {
|
||||
export interface InputBoxProperties {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface DropDownProperties {
|
||||
value?: string;
|
||||
values?: string[];
|
||||
}
|
||||
|
||||
export interface ButtonProperties {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface CardComponent extends Component {
|
||||
label: string;
|
||||
value: string;
|
||||
@@ -174,6 +208,17 @@ declare module 'sqlops' {
|
||||
onTextChanged: vscode.Event<any>;
|
||||
}
|
||||
|
||||
export interface DropDownComponent extends Component {
|
||||
value: string;
|
||||
values: string[];
|
||||
onValueChanged: vscode.Event<any>;
|
||||
}
|
||||
|
||||
export interface ButtonComponent extends Component {
|
||||
label: string;
|
||||
onDidClick: vscode.Event<any>;
|
||||
}
|
||||
|
||||
export interface WidgetComponent extends Component {
|
||||
widgetId: string;
|
||||
}
|
||||
@@ -242,6 +287,16 @@ declare module 'sqlops' {
|
||||
*/
|
||||
export function createButton(label: string): Button;
|
||||
|
||||
/**
|
||||
* Opens the given dialog if it is not already open
|
||||
*/
|
||||
export function openDialog(dialog: Dialog): void;
|
||||
|
||||
/**
|
||||
* Closes the given dialog if it is open
|
||||
*/
|
||||
export function closeDialog(dialog: Dialog): void;
|
||||
|
||||
// Model view dialog classes
|
||||
export interface Dialog {
|
||||
/**
|
||||
@@ -252,79 +307,52 @@ declare module 'sqlops' {
|
||||
/**
|
||||
* The content of the dialog. If multiple tabs are given they will be displayed with tabs
|
||||
* If a string is given, it should be the ID of the dialog's model view content
|
||||
* TODO mairvine 4/18/18: use a model view content type
|
||||
*/
|
||||
content: string | DialogTab[],
|
||||
|
||||
/**
|
||||
* The caption of the OK button
|
||||
* The ok button
|
||||
*/
|
||||
okTitle: string;
|
||||
okButton: Button;
|
||||
|
||||
/**
|
||||
* The caption of the Cancel button
|
||||
* The cancel button
|
||||
*/
|
||||
cancelTitle: string;
|
||||
cancelButton: Button;
|
||||
|
||||
/**
|
||||
* Any additional buttons that should be displayed
|
||||
*/
|
||||
customButtons: Button[];
|
||||
|
||||
/**
|
||||
* Opens the dialog
|
||||
*/
|
||||
open(): void;
|
||||
|
||||
/**
|
||||
* Closes the dialog
|
||||
*/
|
||||
close(): void;
|
||||
|
||||
/**
|
||||
* Updates the dialog on screen to reflect changes to the buttons or content
|
||||
*/
|
||||
updateContent(): void;
|
||||
|
||||
/**
|
||||
* Raised when dialog's ok button is pressed
|
||||
*/
|
||||
readonly onOk: vscode.Event<void>;
|
||||
|
||||
/**
|
||||
* Raised when dialog is canceled
|
||||
*/
|
||||
readonly onCancel: vscode.Event<void>;
|
||||
}
|
||||
|
||||
export interface DialogTab {
|
||||
/**
|
||||
* The title of the tab
|
||||
*/
|
||||
title: string,
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* A string giving the ID of the tab's model view content
|
||||
* TODO mairvine 4/18/18: use a model view content type
|
||||
*/
|
||||
content: string;
|
||||
|
||||
/**
|
||||
* Updates the dialog on screen to reflect changes to the content
|
||||
*/
|
||||
updateContent(): void;
|
||||
}
|
||||
|
||||
export interface Button {
|
||||
/**
|
||||
* The label displayed on the button
|
||||
*/
|
||||
label: string,
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Whether the button is enabled
|
||||
*/
|
||||
enabled: boolean,
|
||||
enabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether the button is hidden
|
||||
*/
|
||||
hidden: boolean;
|
||||
|
||||
/**
|
||||
* Raised when the button is clicked
|
||||
|
||||
@@ -68,14 +68,17 @@ export enum ModelComponentTypes {
|
||||
FlexContainer,
|
||||
Card,
|
||||
InputBox,
|
||||
DropDown,
|
||||
Button,
|
||||
DashboardWidget,
|
||||
DashboardWebview
|
||||
DashboardWebview,
|
||||
Form
|
||||
}
|
||||
|
||||
export interface IComponentShape {
|
||||
type: ModelComponentTypes;
|
||||
id: string;
|
||||
properties?: { [key: string]: any };
|
||||
properties?: { [key: string]: any };
|
||||
layout?: any;
|
||||
itemConfigs?: IItemConfig[];
|
||||
}
|
||||
@@ -87,10 +90,30 @@ export interface IItemConfig {
|
||||
|
||||
export enum ComponentEventType {
|
||||
PropertiesChanged,
|
||||
onDidChange
|
||||
onDidChange,
|
||||
onDidClick
|
||||
}
|
||||
|
||||
export interface IComponentEventArgs {
|
||||
eventType: ComponentEventType;
|
||||
args: any;
|
||||
}
|
||||
|
||||
export interface IModelViewDialogDetails {
|
||||
title: string;
|
||||
content: string | number[];
|
||||
okButton: number;
|
||||
cancelButton: number;
|
||||
customButtons: number[];
|
||||
}
|
||||
|
||||
export interface IModelViewTabDetails {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface IModelViewButtonDetails {
|
||||
label: string;
|
||||
enabled: boolean;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
return new ContainerBuilderImpl<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
|
||||
}
|
||||
|
||||
formContainer(): sqlops.FormBuilder {
|
||||
let id = this.getNextComponentId();
|
||||
return new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id);
|
||||
}
|
||||
|
||||
card(): sqlops.ComponentBuilder<sqlops.CardComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new CardWrapper(this._proxy, this._handle, id), id);
|
||||
@@ -43,6 +48,16 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
return this.withEventHandler(new InputBoxWrapper(this._proxy, this._handle, id), id);
|
||||
}
|
||||
|
||||
button(): sqlops.ComponentBuilder<sqlops.ButtonComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new ButtonWrapper(this._proxy, this._handle, id), id);
|
||||
}
|
||||
|
||||
dropDown(): sqlops.ComponentBuilder<sqlops.DropDownComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new DropDownWrapper(this._proxy, this._handle, id), id);
|
||||
}
|
||||
|
||||
dashboardWidget(widgetId: string): sqlops.ComponentBuilder<sqlops.WidgetComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler<sqlops.WidgetComponent>(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id);
|
||||
@@ -122,9 +137,40 @@ class ContainerBuilderImpl<T extends sqlops.Component, TLayout, TItemLayout> ext
|
||||
}
|
||||
}
|
||||
|
||||
class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sqlops.FormLayout, sqlops.FormItemLayout> {
|
||||
|
||||
withFormItems(components: sqlops.FormComponent[], itemLayout?: sqlops.FormItemLayout): sqlops.ContainerBuilder<sqlops.FormContainer, sqlops.FormLayout, sqlops.FormItemLayout> {
|
||||
|
||||
this._component.itemConfigs = components.map(item => {
|
||||
let componentWrapper = item.component as ComponentWrapper;
|
||||
let actions: string[] = undefined;
|
||||
if (item.actions) {
|
||||
actions = item.actions.map(action => {
|
||||
let actionComponentWrapper = action as ComponentWrapper;
|
||||
return actionComponentWrapper.id;
|
||||
});
|
||||
}
|
||||
return new InternalItemConfig(componentWrapper, Object.assign({}, itemLayout, {
|
||||
title: item.title,
|
||||
actions: actions,
|
||||
isFormComponent: true
|
||||
}));
|
||||
});
|
||||
|
||||
components.forEach(formItem => {
|
||||
if (formItem.actions) {
|
||||
formItem.actions.forEach(component => {
|
||||
let componentWrapper = component as ComponentWrapper;
|
||||
this._component.itemConfigs.push(new InternalItemConfig(componentWrapper, itemLayout));
|
||||
});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class InternalItemConfig {
|
||||
constructor(private _component: ComponentWrapper, public config: any) {}
|
||||
constructor(private _component: ComponentWrapper, public config: any) { }
|
||||
|
||||
public toIItemConfig(): IItemConfig {
|
||||
return {
|
||||
@@ -146,6 +192,7 @@ class ComponentWrapper implements sqlops.Component {
|
||||
|
||||
private _onErrorEmitter = new Emitter<Error>();
|
||||
public readonly onError: vscode.Event<Error> = this._onErrorEmitter.event;
|
||||
protected _emitterMap = new Map<ComponentEventType, Emitter<any>>();
|
||||
|
||||
constructor(protected readonly _proxy: MainThreadModelViewShape,
|
||||
protected readonly _handle: number,
|
||||
@@ -169,7 +216,7 @@ class ComponentWrapper implements sqlops.Component {
|
||||
}
|
||||
|
||||
public toComponentShape(): IComponentShape {
|
||||
return <IComponentShape> {
|
||||
return <IComponentShape>{
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
layout: this.layout,
|
||||
@@ -183,13 +230,13 @@ class ComponentWrapper implements sqlops.Component {
|
||||
return this._proxy.$clearContainer(this._handle, this.id);
|
||||
}
|
||||
|
||||
public addItems(items: Array<sqlops.Component>, itemLayout ?: any): void {
|
||||
for(let item of items) {
|
||||
public addItems(items: Array<sqlops.Component>, itemLayout?: any): void {
|
||||
for (let item of items) {
|
||||
this.addItem(item, itemLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public addItem(item: sqlops.Component, itemLayout ?: any): void {
|
||||
public addItem(item: sqlops.Component, itemLayout?: any): void {
|
||||
let itemImpl = item as ComponentWrapper;
|
||||
if (!itemImpl) {
|
||||
throw new Error(nls.localize('unknownComponentType', 'Unkown component type. Must use ModelBuilder to create objects'));
|
||||
@@ -218,7 +265,12 @@ class ComponentWrapper implements sqlops.Component {
|
||||
public onEvent(eventArgs: IComponentEventArgs) {
|
||||
if (eventArgs && eventArgs.eventType === ComponentEventType.PropertiesChanged) {
|
||||
this.properties = eventArgs.args;
|
||||
}
|
||||
} else if (eventArgs) {
|
||||
let emitter = this._emitterMap.get(eventArgs.eventType);
|
||||
if (emitter) {
|
||||
emitter.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected setProperty(key: string, value: any): Thenable<boolean> {
|
||||
@@ -278,9 +330,6 @@ class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxCompone
|
||||
this._emitterMap.set(ComponentEventType.onDidChange, new Emitter<any>());
|
||||
}
|
||||
|
||||
private _onTextChangedEmitter = new Emitter<any>();
|
||||
private _emitterMap = new Map<ComponentEventType, Emitter<any>>();
|
||||
|
||||
public get value(): string {
|
||||
return this.properties['value'];
|
||||
}
|
||||
@@ -292,15 +341,54 @@ class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxCompone
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
|
||||
return emitter && emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
public onEvent(eventArgs: IComponentEventArgs) {
|
||||
super.onEvent(eventArgs);
|
||||
if (eventArgs) {
|
||||
let emitter = this._emitterMap.get(eventArgs.eventType);
|
||||
if (emitter) {
|
||||
emitter.fire();
|
||||
}
|
||||
}
|
||||
class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownComponent {
|
||||
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||
super(proxy, handle, ModelComponentTypes.DropDown, id);
|
||||
this.properties = {};
|
||||
this._emitterMap.set(ComponentEventType.onDidChange, new Emitter<any>());
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.properties['value'];
|
||||
}
|
||||
public set value(v: string) {
|
||||
this.setProperty('value', v);
|
||||
}
|
||||
|
||||
public get values(): string[] {
|
||||
return this.properties['values'];
|
||||
}
|
||||
public set values(v: string[]) {
|
||||
this.setProperty('values', v);
|
||||
}
|
||||
|
||||
public get onValueChanged(): vscode.Event<any> {
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
|
||||
return emitter && emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonWrapper extends ComponentWrapper implements sqlops.ButtonComponent {
|
||||
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||
super(proxy, handle, ModelComponentTypes.Button, id);
|
||||
this.properties = {};
|
||||
this._emitterMap.set(ComponentEventType.onDidClick, new Emitter<any>());
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.properties['label'];
|
||||
}
|
||||
public set label(v: string) {
|
||||
this.setProperty('label', v);
|
||||
}
|
||||
|
||||
public get onDidClick(): vscode.Event<any> {
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
|
||||
return emitter && emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
211
src/sql/workbench/api/node/extHostModelViewDialog.ts
Normal file
211
src/sql/workbench/api/node/extHostModelViewDialog.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
class DialogImpl implements sqlops.window.modelviewdialog.Dialog {
|
||||
public title: string;
|
||||
public content: string | sqlops.window.modelviewdialog.DialogTab[];
|
||||
public okButton: sqlops.window.modelviewdialog.Button;
|
||||
public cancelButton: sqlops.window.modelviewdialog.Button;
|
||||
public customButtons: sqlops.window.modelviewdialog.Button[];
|
||||
|
||||
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) {
|
||||
this.okButton = this._extHostModelViewDialog.createButton(nls.localize('dialogOkLabel', 'Done'));
|
||||
this.cancelButton = this._extHostModelViewDialog.createButton(nls.localize('dialogCancelLabel', 'Cancel'));
|
||||
}
|
||||
}
|
||||
|
||||
class TabImpl implements sqlops.window.modelviewdialog.DialogTab {
|
||||
public title: string;
|
||||
public content: string;
|
||||
|
||||
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) { }
|
||||
}
|
||||
|
||||
class ButtonImpl implements sqlops.window.modelviewdialog.Button {
|
||||
private _label: string;
|
||||
private _enabled: boolean;
|
||||
private _hidden: boolean;
|
||||
|
||||
private _onClick = new Emitter<void>();
|
||||
public onClick = this._onClick.event;
|
||||
|
||||
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) {
|
||||
this._enabled = true;
|
||||
this._hidden = false;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public set label(label: string) {
|
||||
this._label = label;
|
||||
this._extHostModelViewDialog.updateButton(this);
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(enabled: boolean) {
|
||||
this._enabled = enabled;
|
||||
this._extHostModelViewDialog.updateButton(this);
|
||||
}
|
||||
|
||||
public get hidden(): boolean {
|
||||
return this._hidden;
|
||||
}
|
||||
|
||||
public set hidden(hidden: boolean) {
|
||||
this._hidden = hidden;
|
||||
this._extHostModelViewDialog.updateButton(this);
|
||||
}
|
||||
|
||||
public getOnClickCallback(): () => void {
|
||||
return () => this._onClick.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
private static _currentHandle = 0;
|
||||
|
||||
private readonly _proxy: MainThreadModelViewDialogShape;
|
||||
|
||||
private readonly _dialogHandles = new Map<sqlops.window.modelviewdialog.Dialog, number>();
|
||||
private readonly _tabHandles = new Map<sqlops.window.modelviewdialog.DialogTab, number>();
|
||||
private readonly _buttonHandles = new Map<sqlops.window.modelviewdialog.Button, number>();
|
||||
|
||||
private readonly _onClickCallbacks = new Map<number, () => void>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadModelViewDialog);
|
||||
}
|
||||
|
||||
private static getNewHandle() {
|
||||
let handle = ExtHostModelViewDialog._currentHandle;
|
||||
ExtHostModelViewDialog._currentHandle += 1;
|
||||
return handle;
|
||||
}
|
||||
|
||||
private getDialogHandle(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
let handle = this._dialogHandles.get(dialog);
|
||||
if (handle === undefined) {
|
||||
handle = ExtHostModelViewDialog.getNewHandle();
|
||||
this._dialogHandles.set(dialog, handle);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private getTabHandle(tab: sqlops.window.modelviewdialog.DialogTab) {
|
||||
let handle = this._tabHandles.get(tab);
|
||||
if (handle === undefined) {
|
||||
handle = ExtHostModelViewDialog.getNewHandle();
|
||||
this._tabHandles.set(tab, handle);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private getButtonHandle(button: sqlops.window.modelviewdialog.Button) {
|
||||
let handle = this._buttonHandles.get(button);
|
||||
if (handle === undefined) {
|
||||
handle = ExtHostModelViewDialog.getNewHandle();
|
||||
this._buttonHandles.set(button, handle);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
public $onButtonClick(handle: number): void {
|
||||
this._onClickCallbacks.get(handle)();
|
||||
}
|
||||
|
||||
public open(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||
let handle = this.getDialogHandle(dialog);
|
||||
this.updateDialogContent(dialog);
|
||||
this._proxy.$open(handle);
|
||||
}
|
||||
|
||||
public close(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||
let handle = this.getDialogHandle(dialog);
|
||||
this._proxy.$close(handle);
|
||||
}
|
||||
|
||||
public updateDialogContent(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||
let handle = this.getDialogHandle(dialog);
|
||||
let tabs = dialog.content;
|
||||
if (tabs && typeof tabs !== 'string') {
|
||||
tabs.forEach(tab => this.updateTabContent(tab));
|
||||
}
|
||||
if (dialog.customButtons) {
|
||||
dialog.customButtons.forEach(button => this.updateButton(button));
|
||||
}
|
||||
this.updateButton(dialog.okButton);
|
||||
this.updateButton(dialog.cancelButton);
|
||||
this._proxy.$setDialogDetails(handle, {
|
||||
title: dialog.title,
|
||||
okButton: this.getButtonHandle(dialog.okButton),
|
||||
cancelButton: this.getButtonHandle(dialog.cancelButton),
|
||||
content: dialog.content && typeof dialog.content !== 'string' ? dialog.content.map(tab => this.getTabHandle(tab)) : dialog.content as string,
|
||||
customButtons: dialog.customButtons ? dialog.customButtons.map(button => this.getButtonHandle(button)) : undefined
|
||||
});
|
||||
}
|
||||
|
||||
public updateTabContent(tab: sqlops.window.modelviewdialog.DialogTab): void {
|
||||
let handle = this.getTabHandle(tab);
|
||||
this._proxy.$setTabDetails(handle, {
|
||||
title: tab.title,
|
||||
content: tab.content
|
||||
});
|
||||
}
|
||||
|
||||
public updateButton(button: sqlops.window.modelviewdialog.Button): void {
|
||||
let handle = this.getButtonHandle(button);
|
||||
this._proxy.$setButtonDetails(handle, {
|
||||
label: button.label,
|
||||
enabled: button.enabled,
|
||||
hidden: button.hidden
|
||||
});
|
||||
}
|
||||
|
||||
public registerOnClickCallback(button: sqlops.window.modelviewdialog.Button, callback: () => void) {
|
||||
let handle = this.getButtonHandle(button);
|
||||
this._onClickCallbacks.set(handle, callback);
|
||||
}
|
||||
|
||||
public createDialog(title: string): sqlops.window.modelviewdialog.Dialog {
|
||||
let dialog = new DialogImpl(this);
|
||||
dialog.title = title;
|
||||
this.getDialogHandle(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public createTab(title: string): sqlops.window.modelviewdialog.DialogTab {
|
||||
let tab = new TabImpl(this);
|
||||
tab.title = title;
|
||||
this.getTabHandle(tab);
|
||||
return tab;
|
||||
}
|
||||
|
||||
public createButton(label: string): sqlops.window.modelviewdialog.Button {
|
||||
let button = new ButtonImpl(this);
|
||||
this.getButtonHandle(button);
|
||||
this.registerOnClickCallback(button, button.getOnClickCallback());
|
||||
button.label = label;
|
||||
return button;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi
|
||||
}
|
||||
|
||||
$initializeModel(handle: number, rootComponent: IComponentShape): Thenable<void> {
|
||||
return this.execModelViewAction(handle, (modelView) => {
|
||||
return this.execModelViewAction(handle, (modelView) => {
|
||||
modelView.initializeModel(rootComponent);
|
||||
});
|
||||
}
|
||||
@@ -67,11 +67,13 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi
|
||||
this._proxy.$handleEvent(handle, componentId, eventArgs);
|
||||
}
|
||||
|
||||
$registerEvent(handle: number, componentId: string): Thenable<void> {
|
||||
$registerEvent(handle: number, componentId: string): Thenable<void> {
|
||||
let properties: { [key: string]: any; } = { eventName: this.onEvent };
|
||||
return this.execModelViewAction(handle, (modelView) => {
|
||||
this._register(modelView.onEvent (e => {
|
||||
this.onEvent(handle, componentId, e);
|
||||
this._register(modelView.onEvent(e => {
|
||||
if (e.componentId && e.componentId === componentId) {
|
||||
this.onEvent(handle, componentId, e);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
130
src/sql/workbench/api/node/mainThreadModelViewDialog.ts
Normal file
130
src/sql/workbench/api/node/mainThreadModelViewDialog.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { MainThreadModelViewDialogShape, SqlMainContext, ExtHostModelViewDialogShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { Dialog, DialogTab, DialogButton } from 'sql/platform/dialog/dialogTypes';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { CustomDialogService } from 'sql/platform/dialog/customDialogService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadModelViewDialog)
|
||||
export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape {
|
||||
private readonly _proxy: ExtHostModelViewDialogShape;
|
||||
private readonly _dialogs = new Map<number, Dialog>();
|
||||
private readonly _tabs = new Map<number, DialogTab>();
|
||||
private readonly _buttons = new Map<number, DialogButton>();
|
||||
private _dialogService: CustomDialogService;
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IInstantiationService instatiationService: IInstantiationService,
|
||||
) {
|
||||
this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelViewDialog);
|
||||
this._dialogService = new CustomDialogService(instatiationService);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public $open(handle: number): Thenable<void> {
|
||||
let dialog = this.getDialog(handle);
|
||||
this._dialogService.showDialog(dialog);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public $close(handle: number): Thenable<void> {
|
||||
let dialog = this.getDialog(handle);
|
||||
this._dialogService.closeDialog(dialog);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public $setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void> {
|
||||
let dialog = this._dialogs.get(handle);
|
||||
if (!dialog) {
|
||||
dialog = new Dialog(details.title);
|
||||
let okButton = this.getButton(details.okButton);
|
||||
let cancelButton = this.getButton(details.cancelButton);
|
||||
dialog.okButton = okButton;
|
||||
dialog.cancelButton = cancelButton;
|
||||
this._dialogs.set(handle, dialog);
|
||||
}
|
||||
|
||||
dialog.title = details.title;
|
||||
if (details.content && typeof details.content !== 'string') {
|
||||
dialog.content = details.content.map(tabHandle => this.getTab(tabHandle));
|
||||
} else {
|
||||
dialog.content = details.content as string;
|
||||
}
|
||||
|
||||
if (details.customButtons) {
|
||||
dialog.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public $setTabDetails(handle: number, details: IModelViewTabDetails): Thenable<void> {
|
||||
let tab = this._tabs.get(handle);
|
||||
if (!tab) {
|
||||
tab = new DialogTab(details.title);
|
||||
this._tabs.set(handle, tab);
|
||||
}
|
||||
|
||||
tab.title = details.title;
|
||||
tab.content = details.content;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public $setButtonDetails(handle: number, details: IModelViewButtonDetails): Thenable<void> {
|
||||
let button = this._buttons.get(handle);
|
||||
if (!button) {
|
||||
button = new DialogButton(details.label, details.enabled);
|
||||
button.hidden = details.hidden;
|
||||
button.onClick(() => this.onButtonClick(handle));
|
||||
this._buttons.set(handle, button);
|
||||
} else {
|
||||
button.label = details.label;
|
||||
button.enabled = details.enabled;
|
||||
button.hidden = details.hidden;
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private getDialog(handle: number): Dialog {
|
||||
let dialog = this._dialogs.get(handle);
|
||||
if (!dialog) {
|
||||
throw new Error('No dialog matching the given handle');
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private getTab(handle: number): DialogTab {
|
||||
let tab = this._tabs.get(handle);
|
||||
if (!tab) {
|
||||
throw new Error('No tab matching the given handle');
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
private getButton(handle: number): DialogButton {
|
||||
let button = this._buttons.get(handle);
|
||||
if (!button) {
|
||||
throw new Error('No button matching the given handle');
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private onButtonClick(handle: number): void {
|
||||
this._proxy.$onButtonClick(handle);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import { ExtHostConnectionManagement } from 'sql/workbench/api/node/extHostConne
|
||||
import { ExtHostDashboard } from 'sql/workbench/api/node/extHostDashboard';
|
||||
import { ExtHostObjectExplorer } from 'sql/workbench/api/node/extHostObjectExplorer';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
|
||||
import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewDialog';
|
||||
import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor';
|
||||
|
||||
export interface ISqlExtensionApiFactory {
|
||||
@@ -65,6 +66,7 @@ export function createApiFactory(
|
||||
const extHostWebviewWidgets = rpcProtocol.set(SqlExtHostContext.ExtHostDashboardWebviews, new ExtHostDashboardWebviews(rpcProtocol));
|
||||
const extHostModelView = rpcProtocol.set(SqlExtHostContext.ExtHostModelView, new ExtHostModelView(rpcProtocol));
|
||||
const extHostDashboard = rpcProtocol.set(SqlExtHostContext.ExtHostDashboard, new ExtHostDashboard(rpcProtocol));
|
||||
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol));
|
||||
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
|
||||
|
||||
|
||||
@@ -283,10 +285,21 @@ export function createApiFactory(
|
||||
};
|
||||
|
||||
const modelViewDialog: typeof sqlops.window.modelviewdialog = {
|
||||
// TODO mairvine 4/18/18: Implement the extension layer for custom dialogs
|
||||
createDialog(title: string): sqlops.window.modelviewdialog.Dialog { return undefined; },
|
||||
createTab(title: string): sqlops.window.modelviewdialog.DialogTab { return undefined; },
|
||||
createButton(label: string): sqlops.window.modelviewdialog.Button { return undefined; }
|
||||
createDialog(title: string): sqlops.window.modelviewdialog.Dialog {
|
||||
return extHostModelViewDialog.createDialog(title);
|
||||
},
|
||||
createTab(title: string): sqlops.window.modelviewdialog.DialogTab {
|
||||
return extHostModelViewDialog.createTab(title);
|
||||
},
|
||||
createButton(label: string): sqlops.window.modelviewdialog.Button {
|
||||
return extHostModelViewDialog.createButton(label);
|
||||
},
|
||||
openDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
return extHostModelViewDialog.open(dialog);
|
||||
},
|
||||
closeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
return extHostModelViewDialog.close(dialog);
|
||||
}
|
||||
};
|
||||
|
||||
const window: typeof sqlops.window = {
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'sql/workbench/api/electron-browser/mainThreadDashboard';
|
||||
import 'sql/workbench/api/node/mainThreadDashboardWebview';
|
||||
import 'sql/workbench/api/node/mainThreadQueryEditor';
|
||||
import 'sql/workbench/api/node/mainThreadModelView';
|
||||
import 'sql/workbench/api/node/mainThreadModelViewDialog';
|
||||
import './mainThreadAccountManagement';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { ITaskHandlerDescription } from 'sql/platform/tasks/common/tasks';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape, IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export abstract class ExtHostAccountManagementShape {
|
||||
@@ -448,6 +448,7 @@ export const SqlMainContext = {
|
||||
MainThreadDashboardWebview: createMainId<MainThreadDashboardWebviewShape>('MainThreadDashboardWebview'),
|
||||
MainThreadModelView: createMainId<MainThreadModelViewShape>('MainThreadModelView'),
|
||||
MainThreadDashboard: createMainId<MainThreadDashboardShape>('MainThreadDashboard'),
|
||||
MainThreadModelViewDialog: createMainId<MainThreadModelViewDialogShape>('MainThreadModelViewDialog'),
|
||||
MainThreadQueryEditor: createMainId<MainThreadQueryEditorShape>('MainThreadQueryEditor'),
|
||||
};
|
||||
|
||||
@@ -464,6 +465,7 @@ export const SqlExtHostContext = {
|
||||
ExtHostDashboardWebviews: createExtId<ExtHostDashboardWebviewsShape>('ExtHostDashboardWebviews'),
|
||||
ExtHostModelView: createExtId<ExtHostModelViewShape>('ExtHostModelView'),
|
||||
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'),
|
||||
ExtHostModelViewDialog: createExtId<ExtHostModelViewDialogShape>('ExtHostModelViewDialog'),
|
||||
ExtHostQueryEditor: createExtId<ExtHostQueryEditorShape>('ExtHostQueryEditor')
|
||||
};
|
||||
|
||||
@@ -543,10 +545,21 @@ export interface MainThreadObjectExplorerShape extends IDisposable {
|
||||
$findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<sqlops.NodeInfo[]>;
|
||||
}
|
||||
|
||||
export interface ExtHostModelViewDialogShape {
|
||||
$onButtonClick(handle: number): void;
|
||||
}
|
||||
|
||||
export interface MainThreadModelViewDialogShape extends IDisposable {
|
||||
$open(handle: number): Thenable<void>;
|
||||
$close(handle: number): Thenable<void>;
|
||||
$setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>;
|
||||
$setTabDetails(handle: number, details: IModelViewTabDetails): Thenable<void>;
|
||||
$setButtonDetails(handle: number, details: IModelViewButtonDetails): Thenable<void>;
|
||||
}
|
||||
export interface ExtHostQueryEditorShape {
|
||||
}
|
||||
|
||||
export interface MainThreadQueryEditorShape extends IDisposable {
|
||||
$connect(fileUri: string, connectionId: string): Thenable<void>;
|
||||
$runQuery(fileUri: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
43
src/sqltest/parts/grid/services/sharedServices.test.ts
Normal file
43
src/sqltest/parts/grid/services/sharedServices.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as assert from 'assert';
|
||||
import * as SharedServices from 'sql/parts/grid/services/sharedServices';
|
||||
|
||||
const testText = '<div>test text</div>';
|
||||
|
||||
suite('Grid shared services tests', () => {
|
||||
test('textFormatter should encode HTML when formatting a DBCellValue object', () => {
|
||||
// If I format a DBCellValue object that contains HTML
|
||||
let cellValue = new SharedServices.DBCellValue();
|
||||
cellValue.displayValue = testText;
|
||||
cellValue.isNull = false;
|
||||
let formattedHtml = SharedServices.textFormatter(undefined, undefined, cellValue, undefined, undefined);
|
||||
|
||||
// Then the result is HTML for a span element containing the cell value's display value as plain text
|
||||
verifyFormattedHtml(formattedHtml, testText);
|
||||
});
|
||||
|
||||
test('textFormatter should encode HTML when formatting a string', () => {
|
||||
// If I format a string that contains HTML
|
||||
let formattedHtml = SharedServices.textFormatter(undefined, undefined, testText, undefined, undefined);
|
||||
|
||||
// Then the result is HTML for a span element containing the given text as plain text
|
||||
verifyFormattedHtml(formattedHtml, testText);
|
||||
});
|
||||
});
|
||||
|
||||
function verifyFormattedHtml(formattedHtml: string, expectedText: string): void {
|
||||
// Create an element containing the span returned by the format call
|
||||
let element = document.createElement('div');
|
||||
element.innerHTML = formattedHtml;
|
||||
let spanElement = element.children[0];
|
||||
|
||||
// Verify that the span element's text, not its innerHTML, matches the expected text
|
||||
assert.equal(spanElement.textContent, testText);
|
||||
assert.notEqual(spanElement.innerHTML, testText);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes';
|
||||
import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes';
|
||||
import { Mock, It, Times } from 'typemoq';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { DialogPane } from 'sql/platform/dialog/dialogPane';
|
||||
|
||||
121
src/sqltest/workbench/api/extHostModelViewDialog.test.ts
Normal file
121
src/sqltest/workbench/api/extHostModelViewDialog.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Mock, It, Times } from 'typemoq';
|
||||
import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewDialog';
|
||||
import { MainThreadModelViewDialogShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
'use strict';
|
||||
|
||||
suite('ExtHostModelViewDialog Tests', () => {
|
||||
let extHostModelViewDialog: ExtHostModelViewDialog;
|
||||
let mockProxy: Mock<MainThreadModelViewDialogShape>;
|
||||
|
||||
setup(() => {
|
||||
mockProxy = Mock.ofInstance(<MainThreadModelViewDialogShape>{
|
||||
$open: handle => undefined,
|
||||
$close: handle => undefined,
|
||||
$setDialogDetails: (handle, details) => undefined,
|
||||
$setTabDetails: (handle, details) => undefined,
|
||||
$setButtonDetails: (handle, details) => undefined
|
||||
});
|
||||
let mainContext = <IMainContext>{
|
||||
getProxy: proxyType => mockProxy.object
|
||||
};
|
||||
extHostModelViewDialog = new ExtHostModelViewDialog(mainContext);
|
||||
});
|
||||
|
||||
test('Creating a dialog returns a dialog with initialized ok and cancel buttons and the given title', () => {
|
||||
let title = 'dialog_title';
|
||||
let dialog = extHostModelViewDialog.createDialog(title);
|
||||
|
||||
assert.equal(dialog.title, title);
|
||||
assert.equal(dialog.okButton.enabled, true);
|
||||
assert.equal(dialog.cancelButton.enabled, true);
|
||||
});
|
||||
|
||||
test('Creating a tab returns a tab with the given title', () => {
|
||||
let title = 'tab_title';
|
||||
let tab = extHostModelViewDialog.createTab(title);
|
||||
|
||||
assert.equal(tab.title, title);
|
||||
});
|
||||
|
||||
test('Creating a button returns an enabled button with the given label', () => {
|
||||
let label = 'button_label';
|
||||
let button = extHostModelViewDialog.createButton(label);
|
||||
|
||||
assert.equal(button.label, label);
|
||||
assert.equal(button.enabled, true);
|
||||
});
|
||||
|
||||
test('Opening a dialog updates its tabs and buttons on the main thread', () => {
|
||||
mockProxy.setup(x => x.$open(It.isAny()));
|
||||
mockProxy.setup(x => x.$setDialogDetails(It.isAny(), It.isAny()));
|
||||
mockProxy.setup(x => x.$setTabDetails(It.isAny(), It.isAny()));
|
||||
mockProxy.setup(x => x.$setButtonDetails(It.isAny(), It.isAny()));
|
||||
|
||||
// Create a dialog with 2 tabs and 2 custom buttons
|
||||
let dialogTitle = 'dialog_title';
|
||||
let dialog = extHostModelViewDialog.createDialog(dialogTitle);
|
||||
let tab1Title = 'tab_1';
|
||||
let tab1 = extHostModelViewDialog.createTab(tab1Title);
|
||||
let tab2Title = 'tab_2';
|
||||
let tab2 = extHostModelViewDialog.createTab(tab2Title);
|
||||
dialog.content = [tab1, tab2];
|
||||
let button1Label = 'button_1';
|
||||
let button1 = extHostModelViewDialog.createButton(button1Label);
|
||||
button1.enabled = false;
|
||||
let button2Label = 'button_2';
|
||||
let button2 = extHostModelViewDialog.createButton(button2Label);
|
||||
|
||||
// Open the dialog and verify that the correct main thread methods were called
|
||||
extHostModelViewDialog.open(dialog);
|
||||
mockProxy.verify(x => x.$setButtonDetails(It.isAny(), It.is(details => {
|
||||
return details.enabled === false && details.label === button1Label;
|
||||
})), Times.once());
|
||||
mockProxy.verify(x => x.$setButtonDetails(It.isAny(), It.is(details => {
|
||||
return details.enabled === true && details.label === button2Label;
|
||||
})), Times.once());
|
||||
mockProxy.verify(x => x.$setTabDetails(It.isAny(), It.is(details => {
|
||||
return details.title === tab1Title;
|
||||
})), Times.once());
|
||||
mockProxy.verify(x => x.$setTabDetails(It.isAny(), It.is(details => {
|
||||
return details.title === tab2Title;
|
||||
})), Times.once());
|
||||
mockProxy.verify(x => x.$setDialogDetails(It.isAny(), It.is(details => {
|
||||
return details.title === dialogTitle;
|
||||
})), Times.once());
|
||||
mockProxy.verify(x => x.$open(It.isAny()), Times.once());
|
||||
});
|
||||
|
||||
test('Button clicks are forwarded to the correct button', () => {
|
||||
// Set up the proxy to record button handles
|
||||
let handles = [];
|
||||
mockProxy.setup(x => x.$setButtonDetails(It.isAny(), It.isAny())).callback((handle, details) => handles.push(handle));
|
||||
|
||||
// Set up the buttons to record click events
|
||||
let label1 = 'button_1';
|
||||
let label2 = 'button_2';
|
||||
let button1 = extHostModelViewDialog.createButton(label1);
|
||||
let button2 = extHostModelViewDialog.createButton(label2);
|
||||
let clickEvents = [];
|
||||
button1.onClick(() => clickEvents.push(1));
|
||||
button2.onClick(() => clickEvents.push(2));
|
||||
extHostModelViewDialog.updateButton(button1);
|
||||
extHostModelViewDialog.updateButton(button2);
|
||||
|
||||
// If the main thread sends some notifications that the buttons have been clicked
|
||||
extHostModelViewDialog.$onButtonClick(handles[0]);
|
||||
extHostModelViewDialog.$onButtonClick(handles[1]);
|
||||
extHostModelViewDialog.$onButtonClick(handles[1]);
|
||||
extHostModelViewDialog.$onButtonClick(handles[0]);
|
||||
|
||||
// Then the clicks should have been handled by the expected handlers
|
||||
assert.deepEqual(clickEvents, [1, 2, 2, 1]);
|
||||
});
|
||||
});
|
||||
157
src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts
Normal file
157
src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Mock, It, Times } from 'typemoq';
|
||||
import { MainThreadModelViewDialog } from 'sql/workbench/api/node/mainThreadModelViewDialog';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IModelViewButtonDetails, IModelViewTabDetails, IModelViewDialogDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { CustomDialogService } from 'sql/platform/dialog/customDialogService';
|
||||
import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes';
|
||||
import { ExtHostModelViewDialogShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
'use strict';
|
||||
|
||||
suite('MainThreadModelViewDialog Tests', () => {
|
||||
let mainThreadModelViewDialog: MainThreadModelViewDialog;
|
||||
let mockExtHostModelViewDialog: Mock<ExtHostModelViewDialogShape>;
|
||||
let mockDialogService: Mock<CustomDialogService>;
|
||||
let openedDialog: Dialog;
|
||||
|
||||
// Dialog details
|
||||
let button1Details: IModelViewButtonDetails;
|
||||
let button2Details: IModelViewButtonDetails;
|
||||
let okButtonDetails: IModelViewButtonDetails;
|
||||
let cancelButtonDetails: IModelViewButtonDetails;
|
||||
let tab1Details: IModelViewTabDetails;
|
||||
let tab2Details: IModelViewTabDetails;
|
||||
let dialogDetails: IModelViewDialogDetails;
|
||||
let button1Handle = 1;
|
||||
let button2Handle = 2;
|
||||
let okButtonHandle = 3;
|
||||
let cancelButtonHandle = 4;
|
||||
let tab1Handle = 5;
|
||||
let tab2Handle = 6;
|
||||
let dialogHandle = 7;
|
||||
|
||||
setup(() => {
|
||||
mockExtHostModelViewDialog = Mock.ofInstance(<ExtHostModelViewDialogShape>{
|
||||
$onButtonClick: handle => undefined
|
||||
});
|
||||
let extHostContext = <IExtHostContext>{
|
||||
getProxy: proxyType => mockExtHostModelViewDialog.object
|
||||
};
|
||||
mainThreadModelViewDialog = new MainThreadModelViewDialog(extHostContext, undefined);
|
||||
|
||||
// Set up the mock dialog service
|
||||
mockDialogService = Mock.ofType(CustomDialogService, undefined, undefined);
|
||||
openedDialog = undefined;
|
||||
mockDialogService.setup(x => x.showDialog(It.isAny())).callback(dialog => openedDialog = dialog);
|
||||
(mainThreadModelViewDialog as any)._dialogService = mockDialogService.object;
|
||||
|
||||
// Set up the dialog details
|
||||
button1Details = {
|
||||
label: 'button1',
|
||||
enabled: false,
|
||||
hidden: false
|
||||
};
|
||||
button2Details = {
|
||||
label: 'button2',
|
||||
enabled: true,
|
||||
hidden: false
|
||||
};
|
||||
okButtonDetails = {
|
||||
label: 'ok_label',
|
||||
enabled: true,
|
||||
hidden: false
|
||||
};
|
||||
cancelButtonDetails = {
|
||||
label: 'cancel_label',
|
||||
enabled: true,
|
||||
hidden: false
|
||||
};
|
||||
tab1Details = {
|
||||
title: 'tab1',
|
||||
content: 'content1'
|
||||
};
|
||||
tab2Details = {
|
||||
title: 'tab2',
|
||||
content: 'content2'
|
||||
};
|
||||
dialogDetails = {
|
||||
title: 'dialog1',
|
||||
content: [tab1Handle, tab2Handle],
|
||||
okButton: okButtonHandle,
|
||||
cancelButton: cancelButtonHandle,
|
||||
customButtons: [button1Handle, button2Handle]
|
||||
};
|
||||
|
||||
// Register the buttons, tabs, and dialog
|
||||
mainThreadModelViewDialog.$setButtonDetails(button1Handle, button1Details);
|
||||
mainThreadModelViewDialog.$setButtonDetails(button2Handle, button2Details);
|
||||
mainThreadModelViewDialog.$setButtonDetails(okButtonHandle, okButtonDetails);
|
||||
mainThreadModelViewDialog.$setButtonDetails(cancelButtonHandle, cancelButtonDetails);
|
||||
mainThreadModelViewDialog.$setTabDetails(tab1Handle, tab1Details);
|
||||
mainThreadModelViewDialog.$setTabDetails(tab2Handle, tab2Details);
|
||||
mainThreadModelViewDialog.$setDialogDetails(dialogHandle, dialogDetails);
|
||||
});
|
||||
|
||||
test('Creating a dialog and calling open on it causes a dialog with correct content and buttons to open', () => {
|
||||
// If I open the dialog
|
||||
mainThreadModelViewDialog.$open(dialogHandle);
|
||||
|
||||
// Then the opened dialog's content and buttons match what was set
|
||||
mockDialogService.verify(x => x.showDialog(It.isAny()), Times.once());
|
||||
assert.notEqual(openedDialog, undefined);
|
||||
assert.equal(openedDialog.title, dialogDetails.title);
|
||||
assert.equal(openedDialog.okButton.label, okButtonDetails.label);
|
||||
assert.equal(openedDialog.okButton.enabled, okButtonDetails.enabled);
|
||||
assert.equal(openedDialog.cancelButton.label, cancelButtonDetails.label);
|
||||
assert.equal(openedDialog.cancelButton.enabled, cancelButtonDetails.enabled);
|
||||
assert.equal(openedDialog.customButtons.length, 2);
|
||||
assert.equal(openedDialog.customButtons[0].label, button1Details.label);
|
||||
assert.equal(openedDialog.customButtons[0].enabled, button1Details.enabled);
|
||||
assert.equal(openedDialog.customButtons[1].label, button2Details.label);
|
||||
assert.equal(openedDialog.customButtons[1].enabled, button2Details.enabled);
|
||||
assert.equal(openedDialog.content.length, 2);
|
||||
assert.equal((openedDialog.content[0] as DialogTab).content, tab1Details.content);
|
||||
assert.equal((openedDialog.content[0] as DialogTab).title, tab1Details.title);
|
||||
assert.equal((openedDialog.content[1] as DialogTab).content, tab2Details.content);
|
||||
assert.equal((openedDialog.content[1] as DialogTab).title, tab2Details.title);
|
||||
});
|
||||
|
||||
test('Button presses are forwarded to the extension host', () => {
|
||||
// Set up the mock proxy to capture button presses
|
||||
let pressedHandles = [];
|
||||
mockExtHostModelViewDialog.setup(x => x.$onButtonClick(It.isAny())).callback(handle => pressedHandles.push(handle));
|
||||
|
||||
// Open the dialog so that its buttons can be accessed
|
||||
mainThreadModelViewDialog.$open(dialogHandle);
|
||||
|
||||
// Set up click emitters for each button
|
||||
let okEmitter = new Emitter<void>();
|
||||
let cancelEmitter = new Emitter<void>();
|
||||
let button1Emitter = new Emitter<void>();
|
||||
let button2Emitter = new Emitter<void>();
|
||||
openedDialog.okButton.registerClickEvent(okEmitter.event);
|
||||
openedDialog.cancelButton.registerClickEvent(cancelEmitter.event);
|
||||
openedDialog.customButtons[0].registerClickEvent(button1Emitter.event);
|
||||
openedDialog.customButtons[1].registerClickEvent(button2Emitter.event);
|
||||
|
||||
// Click the buttons
|
||||
button1Emitter.fire();
|
||||
button2Emitter.fire();
|
||||
okEmitter.fire();
|
||||
cancelEmitter.fire();
|
||||
button2Emitter.fire();
|
||||
cancelEmitter.fire();
|
||||
button1Emitter.fire();
|
||||
okEmitter.fire();
|
||||
|
||||
// Verify that the correct button click notifications were sent to the proxy
|
||||
assert.deepEqual(pressedHandles, [button1Handle, button2Handle, okButtonHandle, cancelButtonHandle, button2Handle, cancelButtonHandle, button1Handle, okButtonHandle]);
|
||||
});
|
||||
});
|
||||
@@ -462,11 +462,12 @@ export class IssueReporter extends Disposable {
|
||||
response.json().then(result => {
|
||||
this.clearSearchResults();
|
||||
|
||||
if (result && result.candidates) {
|
||||
this.displaySearchResults(result.candidates);
|
||||
} else {
|
||||
throw new Error('Unexpected response, no candidates property');
|
||||
}
|
||||
// {{SQL CARBON EDIT}}
|
||||
// if (result && result.candidates) {
|
||||
// this.displaySearchResults(result.candidates);
|
||||
// } else {
|
||||
// throw new Error('Unexpected response, no candidates property');
|
||||
// }
|
||||
}).catch((error) => {
|
||||
this.logSearchError(error);
|
||||
});
|
||||
|
||||
@@ -59,13 +59,14 @@ export class IssueReporterModel {
|
||||
assign(this._data, newData);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
serialize(): string {
|
||||
return `
|
||||
Issue Type: <b>${this.getIssueTypeTitle()}</b>
|
||||
|
||||
${this._data.issueDescription}
|
||||
|
||||
VS Code version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion}
|
||||
SQL Operations Studio version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion}
|
||||
OS version: ${this._data.versionInfo && this._data.versionInfo.os}
|
||||
|
||||
${this.getInfos()}
|
||||
|
||||
@@ -23,6 +23,7 @@ suite('IssueReporter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
test('serializes model skeleton when no data is provided', () => {
|
||||
const issueReporterModel = new IssueReporterModel();
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
@@ -31,7 +32,7 @@ Issue Type: <b>Feature Request</b>
|
||||
|
||||
undefined
|
||||
|
||||
VS Code version: undefined
|
||||
SQL Operations Studio version: undefined
|
||||
OS version: undefined
|
||||
|
||||
|
||||
|
||||
@@ -435,7 +435,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
return this.queryGallery(query).then(({ galleryExtensions, total }) => {
|
||||
const extensions = galleryExtensions.map((e, index) => toExtension(e, this.extensionsGalleryUrl, index, query, options.source));
|
||||
const pageSize = query.pageSize;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
const pageSize = extensions.length;
|
||||
const getPage = (pageIndex: number) => {
|
||||
const nextPageQuery = query.withPage(pageIndex + 1);
|
||||
return this.queryGallery(nextPageQuery)
|
||||
|
||||
@@ -31,7 +31,18 @@ function getClient(aiKey: string): typeof appInsights.client {
|
||||
|
||||
const client = appInsights.getClient(aiKey);
|
||||
client.channel.setOfflineMode(true);
|
||||
client.context.tags[client.context.keys.deviceMachineName] = ''; //prevent App Insights from reporting machine name
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// clear all ID fields from telemetry
|
||||
client.context.tags[client.context.keys.deviceMachineName] = '';
|
||||
client.context.tags[client.context.keys.cloudRoleInstance] = '';
|
||||
|
||||
// set envelope flags to suppress Vortex ingest header
|
||||
client.addTelemetryProcessor((envelope, contextObjects) => {
|
||||
envelope.flags = 0x200000;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (aiKey.indexOf('AIF-') === 0) {
|
||||
client.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1';
|
||||
}
|
||||
|
||||
@@ -14,10 +14,15 @@ import product from 'vs/platform/node/product';
|
||||
|
||||
export function resolveCommonProperties(commit: string, version: string, machineId: string, installSourcePath: string): TPromise<{ [name: string]: string; }> {
|
||||
const result: { [name: string]: string; } = Object.create(null);
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
|
||||
result['common.machineId'] = machineId;
|
||||
// __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
result['sessionID'] = uuid.generateUuid() + Date.now();
|
||||
// result['common.machineId'] = machineId;
|
||||
result['common.machineId'] = '';
|
||||
// // __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
// result['sessionID'] = uuid.generateUuid() + Date.now();
|
||||
result['sessionID'] = '';
|
||||
|
||||
// __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
result['commitHash'] = commit;
|
||||
// __GDPR__COMMON__ "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
@@ -32,7 +37,7 @@ export function resolveCommonProperties(commit: string, version: string, machine
|
||||
result['common.nodePlatform'] = process.platform;
|
||||
// __GDPR__COMMON__ "common.nodeArch" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
result['common.nodeArch'] = process.arch;
|
||||
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
result['common.application.name'] = product.nameLong;
|
||||
|
||||
|
||||
@@ -20,48 +20,34 @@ export function resolveWorkbenchCommonProperties(storageService: IStorageService
|
||||
result['common.version.renderer'] = process.versions && (<any>process).versions['chrome'];
|
||||
// {{SQL CARBON EDIT}}
|
||||
result['common.application.name'] = product.nameLong;
|
||||
getUserId(storageService).then(value => result['common.userId'] = value);
|
||||
// {{SQL CARBON EDIT}}
|
||||
result['common.userId'] = '';
|
||||
|
||||
const lastSessionDate = storageService.get('telemetry.lastSessionDate');
|
||||
const firstSessionDate = storageService.get('telemetry.firstSessionDate') || new Date().toUTCString();
|
||||
storageService.store('telemetry.firstSessionDate', firstSessionDate);
|
||||
storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
|
||||
// {{SQL CARBON EDIT}}
|
||||
// const lastSessionDate = storageService.get('telemetry.lastSessionDate');
|
||||
// const firstSessionDate = storageService.get('telemetry.firstSessionDate') || new Date().toUTCString();
|
||||
// storageService.store('telemetry.firstSessionDate', firstSessionDate);
|
||||
// storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
|
||||
|
||||
// __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
result['common.firstSessionDate'] = firstSessionDate;
|
||||
// __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
result['common.lastSessionDate'] = lastSessionDate;
|
||||
// __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
result['common.isNewSession'] = !lastSessionDate ? '1' : '0';
|
||||
// // __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
// result['common.firstSessionDate'] = firstSessionDate;
|
||||
// // __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
// result['common.lastSessionDate'] = lastSessionDate;
|
||||
// // __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
// result['common.isNewSession'] = !lastSessionDate ? '1' : '0';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// __GDPR__COMMON__ "common.instanceId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
|
||||
result['common.instanceId'] = getOrCreateInstanceId(storageService);
|
||||
// result['common.instanceId'] = getOrCreateInstanceId(storageService);
|
||||
result['common.instanceId'] = '';
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function getOrCreateInstanceId(storageService: IStorageService): string {
|
||||
const result = storageService.get('telemetry.instanceId') || uuid.generateUuid();
|
||||
storageService.store('telemetry.instanceId', result);
|
||||
|
||||
return result;
|
||||
}
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Get the unique ID for the current user
|
||||
function getUserId(storageService: IStorageService): Promise<string> {
|
||||
var userId = storageService.get('common.userId');
|
||||
return new Promise<string>(resolve => {
|
||||
// Generate the user id if it has not been created already
|
||||
if (typeof userId === 'undefined') {
|
||||
let id = Utils.generateUserId();
|
||||
id.then( newId => {
|
||||
userId = newId;
|
||||
resolve(userId);
|
||||
//store the user Id in the storage service
|
||||
storageService.store('common.userId', userId);
|
||||
});
|
||||
} else {
|
||||
resolve(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
// function getOrCreateInstanceId(storageService: IStorageService): string {
|
||||
// const result = storageService.get('telemetry.instanceId') || uuid.generateUuid();
|
||||
// storageService.store('telemetry.instanceId', result);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
@@ -32,58 +32,59 @@ suite('Telemetry - common properties', function () {
|
||||
del(parentDir, os.tmpdir(), done);
|
||||
});
|
||||
|
||||
test('default', function () {
|
||||
return mkdirp(parentDir).then(() => {
|
||||
fs.writeFileSync(installSource, 'my.install.source');
|
||||
// {{SQL CARBON EDIT}}
|
||||
// test('default', function () {
|
||||
// return mkdirp(parentDir).then(() => {
|
||||
// fs.writeFileSync(installSource, 'my.install.source');
|
||||
|
||||
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
|
||||
assert.ok('commitHash' in props);
|
||||
assert.ok('sessionID' in props);
|
||||
assert.ok('timestamp' in props);
|
||||
assert.ok('common.platform' in props);
|
||||
assert.ok('common.nodePlatform' in props);
|
||||
assert.ok('common.nodeArch' in props);
|
||||
assert.ok('common.timesincesessionstart' in props);
|
||||
assert.ok('common.sequence' in props);
|
||||
// return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
|
||||
// assert.ok('commitHash' in props);
|
||||
// assert.ok('sessionID' in props);
|
||||
// assert.ok('timestamp' in props);
|
||||
// assert.ok('common.platform' in props);
|
||||
// assert.ok('common.nodePlatform' in props);
|
||||
// assert.ok('common.nodeArch' in props);
|
||||
// assert.ok('common.timesincesessionstart' in props);
|
||||
// assert.ok('common.sequence' in props);
|
||||
|
||||
// assert.ok('common.version.shell' in first.data); // only when running on electron
|
||||
// assert.ok('common.version.renderer' in first.data);
|
||||
assert.ok('common.osVersion' in props, 'osVersion');
|
||||
assert.ok('common.platformVersion' in props, 'platformVersion');
|
||||
assert.ok('version' in props);
|
||||
assert.equal(props['common.source'], 'my.install.source');
|
||||
// // assert.ok('common.version.shell' in first.data); // only when running on electron
|
||||
// // assert.ok('common.version.renderer' in first.data);
|
||||
// assert.ok('common.osVersion' in props, 'osVersion');
|
||||
// assert.ok('common.platformVersion' in props, 'platformVersion');
|
||||
// assert.ok('version' in props);
|
||||
// assert.equal(props['common.source'], 'my.install.source');
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
assert.ok('common.application.name' in props);
|
||||
// // {{SQL CARBON EDIT}}
|
||||
// assert.ok('common.application.name' in props);
|
||||
|
||||
assert.ok('common.firstSessionDate' in props, 'firstSessionDate');
|
||||
assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); // conditional, see below, 'lastSessionDate'ow
|
||||
assert.ok('common.isNewSession' in props, 'isNewSession');
|
||||
// assert.ok('common.firstSessionDate' in props, 'firstSessionDate');
|
||||
// assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); // conditional, see below, 'lastSessionDate'ow
|
||||
// assert.ok('common.isNewSession' in props, 'isNewSession');
|
||||
|
||||
// machine id et al
|
||||
assert.ok('common.instanceId' in props, 'instanceId');
|
||||
assert.ok('common.machineId' in props, 'machineId');
|
||||
// // machine id et al
|
||||
// assert.ok('common.instanceId' in props, 'instanceId');
|
||||
// assert.ok('common.machineId' in props, 'machineId');
|
||||
|
||||
fs.unlinkSync(installSource);
|
||||
// fs.unlinkSync(installSource);
|
||||
|
||||
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
|
||||
assert.ok(!('common.source' in props));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
// return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
|
||||
// assert.ok(!('common.source' in props));
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
test('lastSessionDate when aviablale', function () {
|
||||
// test('lastSessionDate when aviablale', function () {
|
||||
|
||||
storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
|
||||
// storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
|
||||
|
||||
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
|
||||
// return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
|
||||
|
||||
assert.ok('common.lastSessionDate' in props); // conditional, see below
|
||||
assert.ok('common.isNewSession' in props);
|
||||
assert.equal(props['common.isNewSession'], 0);
|
||||
});
|
||||
});
|
||||
// assert.ok('common.lastSessionDate' in props); // conditional, see below
|
||||
// assert.ok('common.isNewSession' in props);
|
||||
// assert.equal(props['common.isNewSession'], 0);
|
||||
// });
|
||||
// });
|
||||
|
||||
test('values chance on ask', function () {
|
||||
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
|
||||
|
||||
@@ -177,7 +177,8 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
|
||||
private createDefaultRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
|
||||
return {
|
||||
id: 'extensions.recommendedList',
|
||||
name: localize('recommendedExtensions', "Recommended"),
|
||||
// {{ SQL CARBON EDIT}}
|
||||
name: localize('recommendedExtensions', "Marketplace"),
|
||||
location: ViewLocation.Extensions,
|
||||
ctor: RecommendedExtensionsView,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('defaultRecommendedExtensions')),
|
||||
|
||||
@@ -234,6 +234,9 @@ export class ExtensionsListView extends ViewsViewletPanel {
|
||||
return this.getAllRecommendationsModel(query, options);
|
||||
} else if (ExtensionsListView.isRecommendedExtensionsQuery(query.value)) {
|
||||
return this.getRecommendationsModel(query, options);
|
||||
// {{SQL CARBON EDIT}}
|
||||
} else if (ExtensionsListView.isAllMarketplaceExtensionsQuery(query.value)) {
|
||||
return this.getAllMarketplaceModel(query, options);
|
||||
}
|
||||
|
||||
let text = query.value;
|
||||
@@ -363,6 +366,42 @@ export class ExtensionsListView extends ViewsViewletPanel {
|
||||
});
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
private getAllMarketplaceModel(query: Query, options: IQueryOptions): TPromise<IPagedModel<IExtension>> {
|
||||
const value = query.value.trim().toLowerCase();
|
||||
return this.extensionsWorkbenchService.queryLocal()
|
||||
.then(result => result.filter(e => e.type === LocalExtensionType.User))
|
||||
.then(local => {
|
||||
return this.tipsService.getOtherRecommendations().then((recommmended) => {
|
||||
const installedExtensions = local.map(x => `${x.publisher}.${x.name}`);
|
||||
options = assign(options, { text: value, source: 'searchText' });
|
||||
return TPromise.as(this.extensionsWorkbenchService.queryGallery(options).then((pager) => {
|
||||
// filter out installed extensions
|
||||
pager.firstPage = pager.firstPage.filter((p) => {
|
||||
return installedExtensions.indexOf(`${p.publisher}.${p.name}`) === -1;
|
||||
});
|
||||
|
||||
// sort the marketplace extensions
|
||||
pager.firstPage.sort((a, b) => {
|
||||
let isRecommendedA: boolean = recommmended.indexOf(`${a.publisher}.${a.name}`) > -1;
|
||||
let isRecommendedB: boolean = recommmended.indexOf(`${b.publisher}.${b.name}`) > -1;
|
||||
|
||||
// sort recommeded extensions before other extensions
|
||||
if (isRecommendedA !== isRecommendedB) {
|
||||
return (isRecommendedA && !isRecommendedB) ? -1 : 1;
|
||||
}
|
||||
|
||||
// otherwise sort by name
|
||||
return a.displayName.toLowerCase() < b.displayName.toLowerCase() ? -1 : 1;
|
||||
});
|
||||
pager.total = pager.firstPage.length;
|
||||
pager.pageSize = pager.firstPage.length;
|
||||
return new PagedModel(pager || []);
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions
|
||||
private getTrimmedRecommendations(installedExtensions: string[], value: string, fileBasedRecommendations: string[], otherRecommendations: string[], workpsaceRecommendations: string[], ) {
|
||||
const totalCount = 8;
|
||||
@@ -524,6 +563,11 @@ export class ExtensionsListView extends ViewsViewletPanel {
|
||||
static isKeymapsRecommendedExtensionsQuery(query: string): boolean {
|
||||
return /@recommended:keymaps/i.test(query);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
static isAllMarketplaceExtensionsQuery(query: string): boolean {
|
||||
return /@allmarketplace/i.test(query);
|
||||
}
|
||||
}
|
||||
|
||||
export class InstalledExtensionsView extends ExtensionsListView {
|
||||
@@ -560,7 +604,8 @@ export class BuiltInExtensionsView extends ExtensionsListView {
|
||||
export class RecommendedExtensionsView extends ExtensionsListView {
|
||||
|
||||
async show(query: string): TPromise<IPagedModel<IExtension>> {
|
||||
return super.show(!query.trim() ? '@recommended:all' : '@recommended');
|
||||
// {{SQL CARBON EDIT}}
|
||||
return super.show('@allmarketplace');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user