mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
highlight problematic property in the designer when error is selected (#18512)
* navigate to property when selecting error message * use list component * highlight problematic property * remove unnecessary call * comment * comment
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerEditPath, DesignerViewModel, DesignerDataPropertyInfo,
|
DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerPropertyPath, DesignerViewModel, DesignerDataPropertyInfo,
|
||||||
DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties,
|
DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties,
|
||||||
DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, DesignerUIState, ScriptProperty, DesignerRootObjectPath
|
DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, DesignerUIState, ScriptProperty, DesignerRootObjectPath
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,10 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
|||||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { DesignerMessagesTabPanelView } from 'sql/workbench/browser/designer/designerMessagesTabPanelView';
|
import { DesignerMessagesTabPanelView } from 'sql/workbench/browser/designer/designerMessagesTabPanelView';
|
||||||
import { DesignerScriptEditorTabPanelView } from 'sql/workbench/browser/designer/designerScriptEditorTabPanelView';
|
import { DesignerScriptEditorTabPanelView } from 'sql/workbench/browser/designer/designerScriptEditorTabPanelView';
|
||||||
|
import { DesignerPropertyPathValidator } from 'sql/workbench/browser/designer/designerPropertyPathValidator';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
|
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||||
|
|
||||||
export interface IDesignerStyle {
|
export interface IDesignerStyle {
|
||||||
tabbedPanelStyles?: ITabbedPanelStyles;
|
tabbedPanelStyles?: ITabbedPanelStyles;
|
||||||
@@ -52,7 +56,7 @@ export interface IDesignerStyle {
|
|||||||
|
|
||||||
export type DesignerUIComponent = InputBox | Checkbox | Table<Slick.SlickData> | SelectBox;
|
export type DesignerUIComponent = InputBox | Checkbox | Table<Slick.SlickData> | SelectBox;
|
||||||
|
|
||||||
export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], parentPath: DesignerEditPath) => DesignerUIComponent[];
|
export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], parentPath: DesignerPropertyPath) => DesignerUIComponent[];
|
||||||
export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void;
|
export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void;
|
||||||
|
|
||||||
const TableRowHeight = 25;
|
const TableRowHeight = 25;
|
||||||
@@ -92,14 +96,15 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||||
@IContextViewService private readonly _contextViewProvider: IContextViewService,
|
@IContextViewService private readonly _contextViewProvider: IContextViewService,
|
||||||
@INotificationService private readonly _notificationService: INotificationService,
|
@INotificationService private readonly _notificationService: INotificationService,
|
||||||
@IDialogService private readonly _dialogService: IDialogService) {
|
@IDialogService private readonly _dialogService: IDialogService,
|
||||||
|
@IThemeService private readonly _themeService: IThemeService) {
|
||||||
super();
|
super();
|
||||||
this._tableCellEditorFactory = new TableCellEditorFactory(
|
this._tableCellEditorFactory = new TableCellEditorFactory(
|
||||||
{
|
{
|
||||||
valueGetter: (item, column): string => {
|
valueGetter: (item, column): string => {
|
||||||
return item[column.field].value;
|
return item[column.field].value;
|
||||||
},
|
},
|
||||||
valueSetter: (parentPath: DesignerEditPath, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
valueSetter: (parentPath: DesignerPropertyPath, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
||||||
this.handleEdit({
|
this.handleEdit({
|
||||||
type: DesignerEditType.Update,
|
type: DesignerEditType.Update,
|
||||||
path: [...parentPath, row, column.field],
|
path: [...parentPath, row, column.field],
|
||||||
@@ -138,7 +143,10 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
onDidChange: Event.None
|
onDidChange: Event.None
|
||||||
}, Sizing.Distribute);
|
}, Sizing.Distribute);
|
||||||
this._scriptTabbedPannel = new TabbedPanel(this._editorContainer);
|
this._scriptTabbedPannel = new TabbedPanel(this._editorContainer);
|
||||||
this._messagesView = new DesignerMessagesTabPanelView();
|
this._messagesView = this._instantiationService.createInstance(DesignerMessagesTabPanelView);
|
||||||
|
this._register(this._messagesView.onMessageSelected((path) => {
|
||||||
|
this.selectProperty(path);
|
||||||
|
}));
|
||||||
this._scriptEditorView = new DesignerScriptEditorTabPanelView(this._instantiationService);
|
this._scriptEditorView = new DesignerScriptEditorTabPanelView(this._instantiationService);
|
||||||
this._scriptTabbedPannel.pushTab({
|
this._scriptTabbedPannel.pushTab({
|
||||||
title: localize('designer.scriptTabTitle', "Scripts"),
|
title: localize('designer.scriptTabTitle', "Scripts"),
|
||||||
@@ -311,6 +319,9 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
private handleEditProcessedEvent(args: DesignerEditProcessedEventArgs): void {
|
private handleEditProcessedEvent(args: DesignerEditProcessedEventArgs): void {
|
||||||
const edit = args.edit;
|
const edit = args.edit;
|
||||||
this._supressEditProcessing = true;
|
this._supressEditProcessing = true;
|
||||||
|
if (!args.result.isValid) {
|
||||||
|
alert(localize('designer.errorCountAlert', "{0} validation errors found.", args.result.errors.length));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.updateComponentValues();
|
this.updateComponentValues();
|
||||||
if (edit.type === DesignerEditType.Add) {
|
if (edit.type === DesignerEditType.Add) {
|
||||||
@@ -407,7 +418,7 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
return rows * TableRowHeight + TableHeaderRowHeight;
|
return rows * TableRowHeight + TableHeaderRowHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePropertiesPane(objectPath: DesignerEditPath): void {
|
private updatePropertiesPane(objectPath: DesignerPropertyPath): void {
|
||||||
let type: string;
|
let type: string;
|
||||||
let components: DesignerDataPropertyInfo[];
|
let components: DesignerDataPropertyInfo[];
|
||||||
let objectViewModel: DesignerViewModel;
|
let objectViewModel: DesignerViewModel;
|
||||||
@@ -469,6 +480,74 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
this._messagesView.updateMessages(this._input.validationErrors);
|
this._messagesView.updateMessages(this._input.validationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private selectProperty(path: DesignerPropertyPath): void {
|
||||||
|
if (!DesignerPropertyPathValidator.validate(path, this._input.viewModel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find top level property
|
||||||
|
let found = false;
|
||||||
|
if (this._input.view.components) {
|
||||||
|
for (const component of this._input.view.components) {
|
||||||
|
if (path[0] === component.propertyName) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._input.view.tabs) {
|
||||||
|
for (const tab of this._input.view.tabs) {
|
||||||
|
if (tab) {
|
||||||
|
for (const component of tab.components) {
|
||||||
|
if (path[0] === component.propertyName) {
|
||||||
|
this._contentTabbedPanel.showTab(tab.title);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
const propertyInfo = this._componentMap.get(<string>path[0]);
|
||||||
|
if (propertyInfo.defintion.componentType !== 'table') {
|
||||||
|
propertyInfo.component.focus();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const tableComponent = <Table<Slick.SlickData>>propertyInfo.component;
|
||||||
|
const targetRow = <number>path[1];
|
||||||
|
const targetCell = 0;
|
||||||
|
tableComponent.setActiveCell(targetRow, targetCell);
|
||||||
|
tableComponent.grid.scrollCellIntoView(targetRow, targetCell, false);
|
||||||
|
if (path.length > 2) {
|
||||||
|
const relativePath = path.slice(2);
|
||||||
|
this._propertiesPane.selectProperty(relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.highlightActiveElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private highlightActiveElement(): void {
|
||||||
|
const bgColor = this._themeService.getColorTheme().getColor(listActiveSelectionBackground);
|
||||||
|
const color = this._themeService.getColorTheme().getColor(listActiveSelectionForeground);
|
||||||
|
const currentElement = document.activeElement as HTMLElement;
|
||||||
|
if (currentElement) {
|
||||||
|
const originalBGColor = currentElement.style.backgroundColor;
|
||||||
|
const originalColor = currentElement.style.color;
|
||||||
|
currentElement.style.backgroundColor = bgColor.toString();
|
||||||
|
currentElement.style.color = color.toString();
|
||||||
|
setTimeout(() => {
|
||||||
|
currentElement.style.color = originalColor;
|
||||||
|
currentElement.style.backgroundColor = originalBGColor;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private handleEdit(edit: DesignerEdit): void {
|
private handleEdit(edit: DesignerEdit): void {
|
||||||
if (this._supressEditProcessing) {
|
if (this._supressEditProcessing) {
|
||||||
return;
|
return;
|
||||||
@@ -553,7 +632,7 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
components: DesignerDataPropertyInfo[],
|
components: DesignerDataPropertyInfo[],
|
||||||
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
||||||
groupHeaders: HTMLElement[],
|
groupHeaders: HTMLElement[],
|
||||||
parentPath: DesignerEditPath,
|
parentPath: DesignerPropertyPath,
|
||||||
area: DesignerUIArea): DesignerUIComponent[] {
|
area: DesignerUIArea): DesignerUIComponent[] {
|
||||||
const uiComponents = [];
|
const uiComponents = [];
|
||||||
const groupNames = [];
|
const groupNames = [];
|
||||||
@@ -589,7 +668,7 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
|
|
||||||
private createComponent(container: HTMLElement,
|
private createComponent(container: HTMLElement,
|
||||||
componentDefinition: DesignerDataPropertyInfo,
|
componentDefinition: DesignerDataPropertyInfo,
|
||||||
parentPath: DesignerEditPath,
|
parentPath: DesignerPropertyPath,
|
||||||
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
||||||
view: DesignerUIArea): DesignerUIComponent {
|
view: DesignerUIArea): DesignerUIComponent {
|
||||||
const propertyPath = [...parentPath, componentDefinition.propertyName];
|
const propertyPath = [...parentPath, componentDefinition.propertyName];
|
||||||
@@ -625,6 +704,7 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
const dropdownContainer = container.appendChild(DOM.$(''));
|
const dropdownContainer = container.appendChild(DOM.$(''));
|
||||||
const dropdownProperties = componentDefinition.componentProperties as DropDownProperties;
|
const dropdownProperties = componentDefinition.componentProperties as DropDownProperties;
|
||||||
const dropdown = new SelectBox(dropdownProperties.values as string[] || [], undefined, this._contextViewProvider, undefined);
|
const dropdown = new SelectBox(dropdownProperties.values as string[] || [], undefined, this._contextViewProvider, undefined);
|
||||||
|
dropdown.setAriaLabel(componentDefinition.componentProperties?.title);
|
||||||
dropdown.render(dropdownContainer);
|
dropdown.render(dropdownContainer);
|
||||||
dropdown.selectElem.style.height = '25px';
|
dropdown.selectElem.style.height = '25px';
|
||||||
dropdown.onDidSelect((e) => {
|
dropdown.onDidSelect((e) => {
|
||||||
@@ -679,6 +759,7 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
addRowButton.icon = {
|
addRowButton.icon = {
|
||||||
id: `add-row-button new codicon`
|
id: `add-row-button new codicon`
|
||||||
};
|
};
|
||||||
|
addRowButton.ariaLabel = localize('designer.newRowButtonAriaLabel', "Add new row to '{0}' table", tableProperties.ariaLabel);
|
||||||
this._buttons.push(addRowButton);
|
this._buttons.push(addRowButton);
|
||||||
}
|
}
|
||||||
const tableContainer = container.appendChild(DOM.$('.full-row'));
|
const tableContainer = container.appendChild(DOM.$('.full-row'));
|
||||||
@@ -695,7 +776,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
rowHeight: TableRowHeight,
|
rowHeight: TableRowHeight,
|
||||||
headerRowHeight: TableHeaderRowHeight
|
headerRowHeight: TableHeaderRowHeight,
|
||||||
|
editorLock: new Slick.EditorLock()
|
||||||
});
|
});
|
||||||
table.ariaLabel = tableProperties.ariaLabel;
|
table.ariaLabel = tableProperties.ariaLabel;
|
||||||
const columns = tableProperties.columns.map(propName => {
|
const columns = tableProperties.columns.map(propName => {
|
||||||
@@ -819,13 +901,15 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
|
|
||||||
private getUIState(): DesignerUIState {
|
private getUIState(): DesignerUIState {
|
||||||
return {
|
return {
|
||||||
activeTabId: this._contentTabbedPanel.activeTabId
|
activeContentTabId: this._contentTabbedPanel.activeTabId,
|
||||||
|
activeScriptTabId: this._scriptTabbedPannel.activeTabId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private restoreUIState(): void {
|
private restoreUIState(): void {
|
||||||
if (this._input.designerUIState) {
|
if (this._input.designerUIState) {
|
||||||
this._contentTabbedPanel.showTab(this._input.designerUIState.activeTabId);
|
this._contentTabbedPanel.showTab(this._input.designerUIState.activeContentTabId);
|
||||||
|
this._scriptTabbedPannel.showTab(this._input.designerUIState.activeScriptTabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,25 +6,118 @@
|
|||||||
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
|
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { DesignerValidationError } from 'sql/workbench/browser/designer/interfaces';
|
import { DesignerPropertyPath, DesignerValidationError } from 'sql/workbench/browser/designer/interfaces';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { IListAccessibilityProvider, List } from 'vs/base/browser/ui/list/listWidget';
|
||||||
|
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||||
|
import { problemsErrorIconForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
|
|
||||||
export class DesignerMessagesTabPanelView extends Disposable implements IPanelView {
|
export class DesignerMessagesTabPanelView extends Disposable implements IPanelView {
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
|
private _onMessageSelected = new Emitter<DesignerPropertyPath>();
|
||||||
|
private _messageList: List<DesignerValidationError>;
|
||||||
|
|
||||||
|
public readonly onMessageSelected: Event<DesignerPropertyPath> = this._onMessageSelected.event;
|
||||||
|
|
||||||
|
constructor(@IThemeService private _themeService: IThemeService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
render(container: HTMLElement): void {
|
render(container: HTMLElement): void {
|
||||||
this._container = container.appendChild(DOM.$('.messages-container'));
|
this._container = container.appendChild(DOM.$('.messages-container'));
|
||||||
|
this._messageList = new List<DesignerValidationError>('designerMessageList', this._container, new DesignerMessageListDelegate(), [new TableFilterListRenderer()], {
|
||||||
|
multipleSelectionSupport: false,
|
||||||
|
keyboardSupport: true,
|
||||||
|
mouseSupport: true,
|
||||||
|
accessibilityProvider: new DesignerMessagesListAccessibilityProvider()
|
||||||
|
});
|
||||||
|
this._register(this._messageList.onDidChangeSelection((e) => {
|
||||||
|
if (e.elements && e.elements.length === 1) {
|
||||||
|
this._onMessageSelected.fire(e.elements[0].propertyPath);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this._register(attachListStyler(this._messageList, this._themeService));
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(dimension: DOM.Dimension): void {
|
layout(dimension: DOM.Dimension): void {
|
||||||
|
this._messageList.layout(dimension.height, dimension.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMessages(errors: DesignerValidationError[]) {
|
updateMessages(errors: DesignerValidationError[]) {
|
||||||
if (this._container) {
|
if (this._messageList) {
|
||||||
DOM.clearNode(this._container);
|
this._messageList.splice(0, this._messageList.length, errors);
|
||||||
errors?.forEach(error => {
|
}
|
||||||
const messageItem = this._container.appendChild(DOM.$('.message-item.codicon.error'));
|
}
|
||||||
messageItem.innerText = error.message;
|
}
|
||||||
|
|
||||||
|
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||||
|
const errorForegroundColor = theme.getColor(problemsErrorIconForeground);
|
||||||
|
if (errorForegroundColor) {
|
||||||
|
collector.addRule(`
|
||||||
|
.designer-component .messages-container .message-item .message-icon {
|
||||||
|
color: ${errorForegroundColor};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const DesignerMessageListTemplateId = 'DesignerMessageListTemplate';
|
||||||
|
class DesignerMessageListDelegate implements IListVirtualDelegate<DesignerValidationError> {
|
||||||
|
getHeight(element: DesignerValidationError): number {
|
||||||
|
return 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplateId(element: DesignerValidationError): string {
|
||||||
|
return DesignerMessageListTemplateId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DesignerMessageListItemTemplate {
|
||||||
|
messageText: HTMLDivElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TableFilterListRenderer implements IListRenderer<DesignerValidationError, DesignerMessageListItemTemplate> {
|
||||||
|
renderTemplate(container: HTMLElement): DesignerMessageListItemTemplate {
|
||||||
|
const data: DesignerMessageListItemTemplate = Object.create(null);
|
||||||
|
const messageItem = container.appendChild(DOM.$('.message-item'));
|
||||||
|
messageItem.appendChild(DOM.$(`.message-icon${Codicon.error.cssSelector}`));
|
||||||
|
data.messageText = messageItem.appendChild(DOM.$('.message-text'));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderElement(element: DesignerValidationError, index: number, templateData: DesignerMessageListItemTemplate, height: number): void {
|
||||||
|
templateData.messageText.innerText = element.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeElement?(element: DesignerValidationError, index: number, templateData: DesignerMessageListItemTemplate, height: number): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public disposeTemplate(templateData: DesignerMessageListItemTemplate): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public get templateId(): string {
|
||||||
|
return DesignerMessageListTemplateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DesignerMessagesListAccessibilityProvider implements IListAccessibilityProvider<DesignerValidationError> {
|
||||||
|
getAriaLabel(element: DesignerValidationError): string {
|
||||||
|
return element.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidgetAriaLabel(): string {
|
||||||
|
return localize('designer.MessageListAriaLabel', "Errors");
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidgetRole() {
|
||||||
|
return 'listbox';
|
||||||
|
}
|
||||||
|
|
||||||
|
getRole(element: DesignerValidationError): string {
|
||||||
|
return 'option';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Table } from 'sql/base/browser/ui/table/table';
|
||||||
import { CreateComponentsFunc, DesignerUIComponent, SetComponentValueFunc } from 'sql/workbench/browser/designer/designer';
|
import { CreateComponentsFunc, DesignerUIComponent, SetComponentValueFunc } from 'sql/workbench/browser/designer/designer';
|
||||||
import { DesignerViewModel, DesignerDataPropertyInfo, DesignerEditPath } from 'sql/workbench/browser/designer/interfaces';
|
import { DesignerViewModel, DesignerDataPropertyInfo, DesignerPropertyPath } from 'sql/workbench/browser/designer/interfaces';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { equals } from 'vs/base/common/objects';
|
import { equals } from 'vs/base/common/objects';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
|
|
||||||
export interface ObjectInfo {
|
export interface ObjectInfo {
|
||||||
path: DesignerEditPath;
|
path: DesignerPropertyPath;
|
||||||
type: string;
|
type: string;
|
||||||
components: DesignerDataPropertyInfo[];
|
components: DesignerDataPropertyInfo[];
|
||||||
viewModel: DesignerViewModel;
|
viewModel: DesignerViewModel;
|
||||||
@@ -19,7 +20,7 @@ export interface ObjectInfo {
|
|||||||
export class DesignerPropertiesPane {
|
export class DesignerPropertiesPane {
|
||||||
private _titleElement: HTMLElement;
|
private _titleElement: HTMLElement;
|
||||||
private _contentElement: HTMLElement;
|
private _contentElement: HTMLElement;
|
||||||
private _objectPath: DesignerEditPath;
|
private _objectPath: DesignerPropertyPath;
|
||||||
private _componentMap = new Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>();
|
private _componentMap = new Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>();
|
||||||
private _groupHeaders: HTMLElement[] = [];
|
private _groupHeaders: HTMLElement[] = [];
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ export class DesignerPropertiesPane {
|
|||||||
return this._componentMap;
|
return this._componentMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get objectPath(): DesignerEditPath {
|
public get objectPath(): DesignerPropertyPath {
|
||||||
return this._objectPath;
|
return this._objectPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,4 +95,22 @@ export class DesignerPropertiesPane {
|
|||||||
});
|
});
|
||||||
this._descriptionContainer.style.display = 'none';
|
this._descriptionContainer.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public selectProperty(path: DesignerPropertyPath): void {
|
||||||
|
const componentInfo = this.componentMap.get(<string>path[0]);
|
||||||
|
if (componentInfo.defintion.componentType !== 'table') {
|
||||||
|
componentInfo.component.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = componentInfo.component as Table<Slick.SlickData>;
|
||||||
|
const row = path[1] as number;
|
||||||
|
let cell = 0;
|
||||||
|
if (path.length === 3) {
|
||||||
|
const colName = path[2] as string;
|
||||||
|
cell = table.columns.findIndex(c => c.field === colName);
|
||||||
|
}
|
||||||
|
table.setActiveCell(row, cell);
|
||||||
|
table.grid.scrollCellIntoView(row, cell, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { DesignerPropertyPath, DesignerTableProperties, DesignerViewModel } from 'sql/workbench/browser/designer/interfaces';
|
||||||
|
|
||||||
|
export class DesignerPropertyPathValidator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the path property, detail of the path can be found in the azdata typings file.
|
||||||
|
* @param path path of the property.
|
||||||
|
* @param viewModel the view model.
|
||||||
|
* @returns Whether the path is valid.
|
||||||
|
*/
|
||||||
|
static validate(path: DesignerPropertyPath, viewModel: DesignerViewModel): boolean {
|
||||||
|
/**
|
||||||
|
* Path specification for all supported scenarios:
|
||||||
|
* 1. 'Add' scenario
|
||||||
|
* a. ['propertyName1']. Example: add a column to the columns property: ['columns'].
|
||||||
|
* b. ['propertyName1',index-1,'propertyName2']. Example: add a column mapping to the first foreign key: ['foreignKeys',0,'mappings'].
|
||||||
|
* 2. 'Update' scenario
|
||||||
|
* a. ['propertyName1']. Example: update the name of the table: ['name'].
|
||||||
|
* b. ['propertyName1',index-1,'propertyName2']. Example: update the name of a column: ['columns',0,'name'].
|
||||||
|
* c. ['propertyName1',index-1,'propertyName2',index-2,'propertyName3']. Example: update the source column of an entry in a foreign key's column mapping table: ['foreignKeys',0,'mappings',0,'source'].
|
||||||
|
* 3. 'Remove' scenario
|
||||||
|
* a. ['propertyName1',index-1]. Example: remove a column from the columns property: ['columns',0'].
|
||||||
|
* b. ['propertyName1',index-1,'propertyName2',index-2]. Example: remove a column mapping from a foreign key's column mapping table: ['foreignKeys',0,'mappings',0].
|
||||||
|
*/
|
||||||
|
if (!path || path.length === 0 || path.length > 5) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < path.length; index++) {
|
||||||
|
const expectingNumber = (index % 2) !== 0;
|
||||||
|
if (expectingNumber && typeof path[index] !== 'number') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectingNumber && typeof path[index] !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let currentObject = viewModel;
|
||||||
|
for (let index = 0; index < path.length;) {
|
||||||
|
const propertyName = <string>path[index];
|
||||||
|
if (Object.keys(currentObject).indexOf(propertyName) === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (index === path.length - 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
const tableData = <DesignerTableProperties>currentObject[propertyName];
|
||||||
|
const objectIndex = <number>path[index];
|
||||||
|
if (!tableData.data || tableData.data.length - 1 < objectIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentObject = tableData.data[objectIndex];
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,7 +81,8 @@ export interface DesignerComponentInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DesignerUIState {
|
export interface DesignerUIState {
|
||||||
activeTabId: PanelTabIdentifier;
|
activeContentTabId: PanelTabIdentifier;
|
||||||
|
activeScriptTabId: PanelTabIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DesignerAction = 'publish' | 'initialize' | 'processEdit' | 'generateScript' | 'generateReport';
|
export type DesignerAction = 'publish' | 'initialize' | 'processEdit' | 'generateScript' | 'generateReport';
|
||||||
@@ -180,7 +181,6 @@ export interface DesignerTableProperties extends ComponentProperties {
|
|||||||
* Whether user can add new rows to the table. The default value is true.
|
* Whether user can add new rows to the table. The default value is true.
|
||||||
*/
|
*/
|
||||||
canAddRows?: boolean;
|
canAddRows?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether user can remove rows from the table. The default value is true.
|
* Whether user can remove rows from the table. The default value is true.
|
||||||
*/
|
*/
|
||||||
@@ -208,14 +208,14 @@ export enum DesignerEditType {
|
|||||||
|
|
||||||
export interface DesignerEdit {
|
export interface DesignerEdit {
|
||||||
type: DesignerEditType;
|
type: DesignerEditType;
|
||||||
path: DesignerEditPath;
|
path: DesignerPropertyPath;
|
||||||
value?: any;
|
value?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DesignerEditPath = (string | number)[];
|
export type DesignerPropertyPath = (string | number)[];
|
||||||
export const DesignerRootObjectPath: DesignerEditPath = [];
|
export const DesignerRootObjectPath: DesignerPropertyPath = [];
|
||||||
|
|
||||||
export type DesignerValidationError = { message: string, property?: DesignerEditPath };
|
export type DesignerValidationError = { message: string, propertyPath?: DesignerPropertyPath };
|
||||||
|
|
||||||
export interface DesignerEditResult {
|
export interface DesignerEditResult {
|
||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
|
|||||||
@@ -30,19 +30,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.designer-component .messages-container {
|
.designer-component .messages-container {
|
||||||
overflow: scroll;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.designer-component .messages-container .message-item {
|
.designer-component .messages-container .message-item {
|
||||||
padding: 0px 5px 0px 25px;
|
display: flex;
|
||||||
background-position: 5px center;
|
}
|
||||||
background-size: 16px 16px;
|
|
||||||
user-select: text;
|
.designer-component .messages-container .message-item .message-icon {
|
||||||
|
margin: 0px 6px;
|
||||||
|
flex: 0 0 auto;
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.designer-component .messages-container .message-item .message-text {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.designer-component .tabbed-panel-container {
|
.designer-component .tabbed-panel-container {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { DesignerViewModel, DesignerEdit, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties, DesignerEditProcessedEventArgs, DesignerAction, DesignerStateChangedEventArgs, DesignerEditPath, DesignerValidationError } from 'sql/workbench/browser/designer/interfaces';
|
import { DesignerViewModel, DesignerEdit, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties, DesignerEditProcessedEventArgs, DesignerAction, DesignerStateChangedEventArgs, DesignerPropertyPath, DesignerValidationError } from 'sql/workbench/browser/designer/interfaces';
|
||||||
import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface';
|
import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { designers } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { designers } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
@@ -658,7 +658,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
|||||||
b. ['propertyName1',index-1,'proper
|
b. ['propertyName1',index-1,'proper
|
||||||
The return values would be the propertyNames followed by slashes in level order. Eg.: propertyName1/propertyName2/...
|
The return values would be the propertyNames followed by slashes in level order. Eg.: propertyName1/propertyName2/...
|
||||||
*/
|
*/
|
||||||
private getObjectTypeFromPath(path: DesignerEditPath): string {
|
private getObjectTypeFromPath(path: DesignerPropertyPath): string {
|
||||||
let typeArray = [];
|
let typeArray = [];
|
||||||
for (let i = 0; i < path.length; i++) {
|
for (let i = 0; i < path.length; i++) {
|
||||||
if (i % 2 === 0) {
|
if (i % 2 === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user