From 972b649beb566de066888a229869fa27f2984dea Mon Sep 17 00:00:00 2001 From: Hale Rankin Date: Fri, 5 Mar 2021 17:34:02 -0800 Subject: [PATCH] Connection error box style fix (#14469) * Modified modal styles, limiting height of basic modal to 480px. * wip - added new attachCalloutDialogStyler. Moved callout-specific styler code out of modal.ts * Moved attach styler code to workbench/common. Added custom styles to imageCalloutDialog * Moved styler code into calloutDialog. Added callout-specific theme colors to colorRegistry. Removed color styles from modal and callout stylesheets. * Added CalloutDialogModal that extends CalloutDialog so that the callout can be instantiated from core. Revised calloutDialog so the position cn be passed in from where it is instantiated. * Revised refactor of modal and image/link callouts so that callout dialog invoked by core can also use the styler. Removed unused properties from dialog code. * Added conditional to dialogModal to use correct styler for callouts. * Cleaned up styles. Modified custom colors. * Wrapped call to positionCalloutDialog in conditional. * Style, colors, styler and modal updates to align callout with latest OPAC toolkit styles. * Moved calloutDialog stylesheet * Consolidated styler code and added a flexible custom styler to provide values for dialogModal * Added image callout code. * Remove image callout dialog until wired fully * Test fixes Co-authored-by: chlafreniere --- .../platform/theme/common/colorRegistry.ts | 8 + .../browser/modal/media/calloutDialog.css | 44 ++--- .../workbench/browser/modal/media/modal.css | 8 +- src/sql/workbench/browser/modal/modal.ts | 173 ++++++++++-------- src/sql/workbench/common/styler.ts | 53 +++++- .../calloutDialog/imageCalloutDialog.ts | 42 +++-- .../calloutDialog/linkCalloutDialog.ts | 40 ++-- .../cellViews/markdownToolbar.component.ts | 16 +- .../calloutDialog/linkCalloutDialog.test.ts | 9 +- .../dialog/browser/customDialogService.ts | 4 +- .../services/dialog/browser/dialogModal.ts | 4 +- 11 files changed, 235 insertions(+), 166 deletions(-) diff --git a/src/sql/platform/theme/common/colorRegistry.ts b/src/sql/platform/theme/common/colorRegistry.ts index 8982b61a64..f9fe005b81 100644 --- a/src/sql/platform/theme/common/colorRegistry.ts +++ b/src/sql/platform/theme/common/colorRegistry.ts @@ -77,3 +77,11 @@ export const infoButtonForeground = registerColor('infoButton.foreground', { dar export const infoButtonBackground = registerColor('infoButton.background', { dark: '#1B1A19', light: '#FFFFFF', hc: '#000000' }, nls.localize('infoButton.background', "Info button background color.")); export const infoButtonBorder = registerColor('infoButton.border', { dark: '#1B1A19', light: '#FFFFFF', hc: contrastBorder }, nls.localize('infoButton.border', "Info button border color.")); export const infoButtonHoverBackground = registerColor('infoButton.hoverBackground', { dark: '#282625', light: '#F3F2F1', hc: '#000000' }, nls.localize('infoButton.hoverBackground', "Info button hover background color.")); + +// Callout Dialog +export const calloutDialogForeground = registerColor('calloutDialog.foreground', { light: '#616161', dark: '#CCCCCC', hc: '#FFFFFF' }, nls.localize('calloutDialogForeground', 'Callout dialog foreground.')); +export const calloutDialogInteriorBorder = registerColor('calloutDialog.interiorBorder', { light: '#D6D6D6', dark: '#323130', hc: '#2B56F2' }, nls.localize('calloutDialogInteriorBorder', "Callout dialog interior borders used for separating elements.")); +export const calloutDialogExteriorBorder = registerColor('calloutDialog.exteriorBorder', { light: '#CCCCCC', dark: '#CCCCCC', hc: '#2B56F2' }, nls.localize('calloutDialogExteriorBorder', "Callout dialog exterior borders to provide contrast against notebook UI.")); +export const calloutDialogHeaderFooterBackground = registerColor('calloutDialog.headerFooterBackground', { light: '#FFFFFF', dark: '#1E1E1E', hc: Color.black }, nls.localize('calloutDialogHeaderFooterBackground', 'Callout dialog header and footer background.')); +export const calloutDialogBodyBackground = registerColor('calloutDialog.bodyBackground', { light: '#FFFFFF', dark: '#1E1E1E', hc: Color.black }, nls.localize('calloutDialogBodyBackground', "Callout dialog body background.")); +export const calloutDialogShadowColor = registerColor('calloutDialog.shadow', { light: '#000000', dark: '#FFFFFF', hc: '#000000' }, nls.localize('calloutDialogShadowColor', 'Callout dialog box shadow color.')); diff --git a/src/sql/workbench/browser/modal/media/calloutDialog.css b/src/sql/workbench/browser/modal/media/calloutDialog.css index 527cc1e8ce..a93108923a 100644 --- a/src/sql/workbench/browser/modal/media/calloutDialog.css +++ b/src/sql/workbench/browser/modal/media/calloutDialog.css @@ -8,7 +8,8 @@ } .modal.callout-dialog .modal-dialog { border-radius: 2px; - box-shadow: 0px 3px 8px rgba(var(--foreground)); + border-width: 1px; + border-style: solid; max-height: 300px; position: absolute; } @@ -28,60 +29,37 @@ } .callout-arrow:before { - border-width: 1px; border-style: solid; - border-color: - transparent - transparent - var(--bodybackground) - var(--bodybackground); - box-shadow: -3px 3px 3px 0 rgba(var(--foreground)); + border-width: 1px; content: ''; display: block; - height: 0; + height: 10px; position: absolute; - width: 0; + width: 10px; } .callout-arrow.from-below:before { - border-width: 0.5em; - left: 2em; - top: -0.2em; + left: 24px; + top: -6px; transform: rotate(135deg); } .callout-arrow.from-above:before { - border-width: 0.5em; - left: 2em; - bottom: -0.2em; + bottom: -6px; + left: 24px; transform: rotate(-45deg); } .callout-arrow.from-left:before { - background-color: var(--bodybackground); height: 26px; - right: -13px; + right: -14px; top: 26px; transform: rotate(-135deg); width: 26px; } .hc-black .callout-arrow:before { - background-color: var(--bodybackground); - border-color: - transparent - transparent - var(--border) - var(--border); - border-width: 0.1em; box-shadow: none; - height: 0.8em; - width: 0.8em; -} -.hc-black .callout-arrow.from-below:before { - top: -0.4em; } .hc-black .callout-arrow.from-left:before { - height: 2em; - right: -1.2em; - width: 2em; + box-shadow: none; } .modal.callout-dialog .modal-header { diff --git a/src/sql/workbench/browser/modal/media/modal.css b/src/sql/workbench/browser/modal/media/modal.css index f6a0646676..61daed1e4c 100644 --- a/src/sql/workbench/browser/modal/media/modal.css +++ b/src/sql/workbench/browser/modal/media/modal.css @@ -21,17 +21,13 @@ .modal:not(.flyout-dialog):not(.callout-dialog) .modal-dialog { margin: auto; width: 640px; - height: 480px; + max-height: 480px; } .modal .modal-header { padding: 15px; } -.modal .modal-footer { - padding: 15px; -} - .modal .codicon.in-progress { width: 25px; height: 25px; @@ -129,8 +125,8 @@ } .modal .modal-footer { - border-top: 1px solid #E1E1E1; display: flex; + padding: 15px; } .modal .modal-footer .left-footer { diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index 16ad1f9d8c..513de71281 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/modal'; +import 'vs/css!./media/calloutDialog'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { Color } from 'vs/base/common/color'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -13,7 +14,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { generateUuid } from 'vs/base/common/uuid'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; - import { Button } from 'sql/base/browser/ui/button/button'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { localize } from 'vs/nls'; @@ -27,9 +27,6 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { editorWidgetForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { notebookToolbarLines } from 'sql/platform/theme/common/colorRegistry'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; export enum MessageLevel { Error = 0, @@ -55,7 +52,9 @@ export interface IModalDialogStyles { footerBackgroundColor?: Color; footerBorderTopWidth?: Color; footerBorderTopStyle?: Color; - footerBorderTopColor?: Color; + dialogInteriorBorder?: Color; + dialogExteriorBorder?: Color; + dialogShadowColor?: Color; } export type DialogWidth = 'narrow' | 'medium' | 'wide' | number | string; @@ -72,8 +71,6 @@ export interface IDialogProperties { export interface IModalOptions { dialogStyle?: DialogStyle; dialogPosition?: DialogPosition; - positionX?: number; - positionY?: number; width?: DialogWidth; isAngular?: boolean; hasBackButton?: boolean; @@ -89,8 +86,6 @@ export interface IModalOptions { const defaultOptions: IModalOptions = { dialogStyle: 'flyout', dialogPosition: undefined, - positionX: undefined, - positionY: undefined, width: 'narrow', isAngular: false, hasBackButton: false, @@ -108,6 +103,7 @@ export type HideReason = 'close' | 'cancel' | 'ok'; export abstract class Modal extends Disposable implements IThemable { protected _useDefaultMessageBoxLocation: boolean = true; + private _styleElement: HTMLStyleElement; protected _messageElement?: HTMLElement; protected _modalOptions: IModalOptions; protected readonly disposableStore = this._register(new DisposableStore()); @@ -132,7 +128,9 @@ export abstract class Modal extends Disposable implements IThemable { private _dialogBorder?: Color; private _dialogHeaderAndFooterBackground?: Color; private _dialogBodyBackground?: Color; - private _footerBorderTopColor?: Color; + private _dialogInteriorBorder?: Color; + private _dialogExteriorBorder?: Color; + private _dialogShadowColor?: Color; private _modalDialog?: HTMLElement; private _modalContent?: HTMLElement; @@ -140,7 +138,7 @@ export abstract class Modal extends Disposable implements IThemable { private _modalBodySection?: HTMLElement; private _modalFooterSection?: HTMLElement; private _closeButtonInHeader?: HTMLElement; - private _bodyContainer?: HTMLElement; + protected _bodyContainer?: HTMLElement; private _modalTitle?: HTMLElement; private _modalTitleIcon?: HTMLElement; private _leftFooter?: HTMLElement; @@ -198,6 +196,8 @@ export abstract class Modal extends Disposable implements IThemable { * */ public render() { + this._styleElement = DOM.createStyleSheet(this._bodyContainer); + let top: number; let builderClass = '.modal.fade'; builderClass += this._modalOptions.dialogStyle === 'flyout' ? '.flyout-dialog' @@ -424,54 +424,55 @@ export abstract class Modal extends Disposable implements IThemable { /** - * Tasks to perform before dialog is shown + * Tasks to perform before callout dialog is shown * Includes: positioning of dialog */ - protected positionDialog(): void { + protected positionCalloutDialog(): void { /** * In the case of 'below', dialog will be positioned beneath the trigger and arrow aligned with trigger. * In the case of 'left', dialog will be positioned left of the trigger and arrow aligned with trigger. */ - if (this._modalOptions.dialogStyle === 'callout') { - let dialogWidth; - if (typeof this._modalOptions.width === 'number') { - dialogWidth = this._modalOptions.width; - } - - if (this._modalOptions.dialogPosition === 'above') { - if (this._modalOptions.dialogProperties) { - this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos - this._modalOptions.dialogProperties.width}px`; - this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos - 235}px`; - } else { - this._modalDialog.style.left = `${this._modalOptions.positionX}px`; - this._modalDialog.style.top = `${this._modalOptions.positionY - 235}px`; - } - } else if (this._modalOptions.dialogPosition === 'below') { - if (this._modalOptions.dialogProperties) { - this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos - this._modalOptions.dialogProperties.width}px`; - this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos + (this._modalOptions.dialogProperties.height)}px`; - } else { - this._modalDialog.style.left = `${this._modalOptions.positionX}px`; - this._modalDialog.style.top = `${this._modalOptions.positionY}px`; - } - } else if (this._modalOptions.dialogPosition === 'left') { - if (this._modalOptions.dialogProperties) { - this._modalDialog.style.left = `${this._modalOptions.positionX - (dialogWidth + this._modalOptions.dialogProperties.width)}px`; - this._modalDialog.style.top = `${this._modalOptions.positionY - this._modalOptions.dialogProperties.height * 2}px`; - } else { - this._modalDialog.style.left = `${this._modalOptions.positionX - (dialogWidth)}px`; - this._modalDialog.style.top = `${this._modalOptions.positionY}px`; - } - } - this._modalDialog.style.width = `${dialogWidth}px`; + let dialogWidth; + if (typeof this._modalOptions.width === 'number') { + dialogWidth = this._modalOptions.width; } + + if (this._modalOptions.dialogPosition === 'above') { + if (this._modalOptions.dialogProperties) { + this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos - this._modalOptions.dialogProperties.width}px`; + this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos - 235}px`; + } else { + this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos}px`; + this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos - 235}px`; + } + } else if (this._modalOptions.dialogPosition === 'below') { + if (this._modalOptions.dialogProperties) { + this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos - this._modalOptions.dialogProperties.width}px`; + this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos + (this._modalOptions.dialogProperties.height)}px`; + } else { + this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos}px`; + this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos}px`; + } + } else if (this._modalOptions.dialogPosition === 'left') { + if (this._modalOptions.dialogProperties) { + this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos - (dialogWidth + this._modalOptions.dialogProperties.width)}px`; + this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos - this._modalOptions.dialogProperties.height * 2}px`; + } else { + this._modalDialog.style.left = `${this._modalOptions.dialogProperties.xPos - (dialogWidth)}px`; + this._modalDialog.style.top = `${this._modalOptions.dialogProperties.yPos}px`; + } + } + + this._modalDialog.style.width = `${dialogWidth}px`; } /** * Shows the modal and attaches key listeners */ protected show() { - this.positionDialog(); + if (this._modalOptions.dialogStyle === 'callout') { + this.positionCalloutDialog(); + } this._focusedElementBeforeOpen = document.activeElement; this._modalShowingContext.get()!.push(this._staticKey); DOM.append(this.layoutService.container, this._bodyContainer!); @@ -689,15 +690,13 @@ export abstract class Modal extends Disposable implements IThemable { * Called by the theme registry on theme change to style the component */ public style(styles: IModalDialogStyles): void { - this._dialogForeground = styles.dialogForeground ? styles.dialogForeground : this._themeService.getColorTheme().getColor(editorWidgetForeground); - this._dialogBorder = styles.dialogBorder ? styles.dialogBorder : this._themeService.getColorTheme().getColor(notebookToolbarLines); - if (this._modalOptions.dialogStyle === 'callout') { - this._dialogHeaderAndFooterBackground = styles.dialogBodyBackground ? styles.dialogBodyBackground : this._themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); - } else { - this._dialogHeaderAndFooterBackground = styles.dialogHeaderAndFooterBackground ? styles.dialogHeaderAndFooterBackground : this._themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); - } - this._dialogBodyBackground = styles.dialogBodyBackground ? styles.dialogBodyBackground : this._themeService.getColorTheme().getColor(editorBackground); - this._footerBorderTopColor = styles.footerBorderTopColor ? styles.footerBorderTopColor : this._themeService.getColorTheme().getColor(notebookToolbarLines); + this._dialogForeground = styles.dialogForeground; + this._dialogBorder = styles.dialogBorder; + this._dialogHeaderAndFooterBackground = styles.dialogHeaderAndFooterBackground; + this._dialogBodyBackground = styles.dialogBodyBackground; + this._dialogInteriorBorder = styles.dialogInteriorBorder; + this._dialogExteriorBorder = styles.dialogExteriorBorder; + this._dialogShadowColor = styles.dialogShadowColor; this.applyStyles(); } @@ -706,10 +705,8 @@ export abstract class Modal extends Disposable implements IThemable { const border = this._dialogBorder ? this._dialogBorder.toString() : ''; const headerAndFooterBackground = this._dialogHeaderAndFooterBackground ? this._dialogHeaderAndFooterBackground.toString() : ''; const bodyBackground = this._dialogBodyBackground ? this._dialogBodyBackground.toString() : ''; - const calloutStyle: CSSStyleDeclaration = this._modalDialog.style; - const footerTopBorderColor = this._footerBorderTopColor ? this._footerBorderTopColor.toString() : ''; - - const foregroundRgb: Color = Color.Format.CSS.parseHex(foreground); + const footerBorderTopWidth = border ? '1px' : ''; + const footerBorderTopStyle = border ? 'solid' : ''; if (this._closeButtonInHeader) { this._closeButtonInHeader.style.color = foreground; @@ -719,17 +716,6 @@ export abstract class Modal extends Disposable implements IThemable { this._modalDialog.style.borderWidth = border ? '1px' : ''; this._modalDialog.style.borderStyle = border ? 'solid' : ''; this._modalDialog.style.borderColor = border; - - calloutStyle.setProperty('--border', `${border}`); - calloutStyle.setProperty('--bodybackground', `${bodyBackground}`); - if (foregroundRgb) { - calloutStyle.setProperty('--foreground', ` - ${foregroundRgb.rgba.r}, - ${foregroundRgb.rgba.g}, - ${foregroundRgb.rgba.b}, - 0.08 - `); - } } if (this._modalHeaderSection) { @@ -754,9 +740,50 @@ export abstract class Modal extends Disposable implements IThemable { if (this._modalFooterSection) { this._modalFooterSection.style.backgroundColor = headerAndFooterBackground; - this._modalFooterSection.style.borderTopWidth = border ? '1px' : ''; - this._modalFooterSection.style.borderTopStyle = border ? 'solid' : ''; - this._modalFooterSection.style.borderTopColor = footerTopBorderColor; + this._modalFooterSection.style.borderTopWidth = footerBorderTopWidth; + this._modalFooterSection.style.borderTopStyle = footerBorderTopStyle; + if (!(this._modalOptions.dialogStyle === 'callout')) { + this._modalFooterSection.style.borderTopColor = border; + } + } + + if (this._modalOptions.dialogStyle === 'callout') { + const content: string[] = []; + const exteriorBorder = this._dialogExteriorBorder ? this._dialogExteriorBorder.toString() : ''; + const exteriorBorderRgb: Color = Color.Format.CSS.parseHex(exteriorBorder); + const shadow = this._dialogShadowColor ? this._dialogShadowColor.toString() : ''; + const shadowRgb: Color = Color.Format.CSS.parseHex(shadow); + + if (exteriorBorderRgb && shadowRgb) { + content.push(` + .modal.callout-dialog .modal-dialog { + border-color: rgba(${exteriorBorderRgb.rgba.r}, ${exteriorBorderRgb.rgba.g}, ${exteriorBorderRgb.rgba.b},0.5); + box-shadow: 0px 3.2px 7.2px rgba(${shadowRgb.rgba.r}, ${shadowRgb.rgba.g}, ${shadowRgb.rgba.b}, 0.132), + 0px 0.6px 1.8px rgba(${shadowRgb.rgba.r}, ${shadowRgb.rgba.g}, ${shadowRgb.rgba.b}, 0.108); + } + .hc-black .modal.callout-dialog .modal-dialog { + border-color: rgba(${exteriorBorderRgb.rgba.r}, ${exteriorBorderRgb.rgba.g}, ${exteriorBorderRgb.rgba.b}, 1); + } + .modal.callout-dialog .modal-footer { + border-top-color: ${this._dialogInteriorBorder}; + } + .callout-arrow:before { + background-color: ${this._dialogBodyBackground}; + border-color: transparent transparent rgba(${exteriorBorderRgb.rgba.r}, ${exteriorBorderRgb.rgba.g}, ${exteriorBorderRgb.rgba.b}, 0.5) rgba(${exteriorBorderRgb.rgba.r}, ${exteriorBorderRgb.rgba.g}, ${exteriorBorderRgb.rgba.b}, 0.5); + } + .hc-black .callout-arrow:before { + border-color: transparent transparent rgba(${exteriorBorderRgb.rgba.r}, ${exteriorBorderRgb.rgba.g}, ${exteriorBorderRgb.rgba.b}, 1) rgba(${exteriorBorderRgb.rgba.r}, ${exteriorBorderRgb.rgba.g}, ${exteriorBorderRgb.rgba.b}, 1); + } + .callout-arrow.from-left:before { + background-color: ${this._dialogBodyBackground}; + box-shadow: -4px 4px 4px rgba(${shadowRgb.rgba.r}, ${shadowRgb.rgba.g}, ${shadowRgb.rgba.b}, 0.05); + }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this._styleElement.innerHTML) { + this._styleElement.innerHTML = newStyles; + } } } diff --git a/src/sql/workbench/common/styler.ts b/src/sql/workbench/common/styler.ts index d3c6e58c6b..ec9983fb3d 100644 --- a/src/sql/workbench/common/styler.ts +++ b/src/sql/workbench/common/styler.ts @@ -6,26 +6,32 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as cr from 'vs/platform/theme/common/colorRegistry'; +import * as sqlcr from 'sql/platform/theme/common/colorRegistry'; import { IThemable } from 'vs/base/common/styler'; -import { attachStyler } from 'vs/platform/theme/common/styler'; +import { attachStyler, IStyleOverrides } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, VERTICAL_TAB_ACTIVE_BACKGROUND, DASHBOARD_BORDER, } from 'vs/workbench/common/theme'; -export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?: - { - dialogForeground?: cr.ColorIdentifier, - dialogHeaderAndFooterBackground?: cr.ColorIdentifier, - dialogBodyBackground?: cr.ColorIdentifier, - }): IDisposable { +export interface IModalDialogStyleOverrides extends IStyleOverrides { + dialogForeground?: cr.ColorIdentifier, + dialogHeaderAndFooterBackground?: cr.ColorIdentifier, + dialogBodyBackground?: cr.ColorIdentifier, + dialogBorder?: cr.ColorIdentifier, + dialogInteriorBorder?: cr.ColorIdentifier, + dialogExteriorBorder?: cr.ColorIdentifier, + dialogShadowColor?: cr.ColorIdentifier +} + +export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?: IModalDialogStyleOverrides): IDisposable { return attachStyler(themeService, { dialogForeground: (style && style.dialogForeground) || cr.foreground, dialogBorder: cr.contrastBorder, dialogHeaderAndFooterBackground: (style && style.dialogHeaderAndFooterBackground) || SIDE_BAR_BACKGROUND, dialogBodyBackground: (style && style.dialogBodyBackground) || cr.editorBackground - }, widget); + } as IModalDialogStyleOverrides, widget); } export function attachPanelStyler(widget: IThemable, themeService: IThemeService) { @@ -49,3 +55,34 @@ export function attachTabbedPanelStyler(widget: IThemable, themeService: IThemeS activeTabContrastBorder: cr.activeContrastBorder }, widget); } + +export function attachCalloutDialogStyler(widget: IThemable, themeService: IThemeService, style?: IModalDialogStyleOverrides): IDisposable { + return attachStyler(themeService, { + dialogForeground: (style && style.dialogForeground) || sqlcr.calloutDialogForeground, + dialogHeaderAndFooterBackground: (style && style.dialogHeaderAndFooterBackground) || sqlcr.calloutDialogHeaderFooterBackground, + dialogBodyBackground: (style && style.dialogBodyBackground) || sqlcr.calloutDialogBodyBackground, + dialogInteriorBorder: (style && style.dialogInteriorBorder) || sqlcr.calloutDialogInteriorBorder, + dialogExteriorBorder: (style && style.dialogExteriorBorder) || sqlcr.calloutDialogExteriorBorder, + dialogShadowColor: (style && style.dialogShadowColor) || sqlcr.calloutDialogShadowColor + } as IModalDialogStyleOverrides, widget); +} + +export function attachCustomDialogStyler(widget: IThemable, themeService: IThemeService, dialogStyle?: string, style?: IModalDialogStyleOverrides): IDisposable { + if (dialogStyle === 'callout') { + return attachStyler(themeService, { + dialogForeground: (style && style.dialogForeground) || sqlcr.calloutDialogForeground, + dialogHeaderAndFooterBackground: (style && style.dialogHeaderAndFooterBackground) || sqlcr.calloutDialogHeaderFooterBackground, + dialogBodyBackground: (style && style.dialogBodyBackground) || sqlcr.calloutDialogBodyBackground, + dialogInteriorBorder: (style && style.dialogInteriorBorder) || sqlcr.calloutDialogInteriorBorder, + dialogExteriorBorder: (style && style.dialogExteriorBorder) || sqlcr.calloutDialogExteriorBorder, + dialogShadowColor: (style && style.dialogShadowColor) || sqlcr.calloutDialogShadowColor + } as IModalDialogStyleOverrides, widget); + } else { + return attachStyler(themeService, { + dialogForeground: (style && style.dialogForeground) || cr.foreground, + dialogBorder: cr.contrastBorder, + dialogHeaderAndFooterBackground: (style && style.dialogHeaderAndFooterBackground) || SIDE_BAR_BACKGROUND, + dialogBodyBackground: (style && style.dialogBodyBackground) || cr.editorBackground + } as IModalDialogStyleOverrides, widget); + } +} diff --git a/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts b/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts index ab3b0240e5..a1149d60a9 100644 --- a/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts +++ b/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts @@ -6,12 +6,12 @@ import 'vs/css!./media/imageCalloutDialog'; import * as DOM from 'vs/base/browser/dom'; import * as styler from 'vs/platform/theme/common/styler'; -import { URI } from 'vs/base/common/uri'; +import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import * as constants from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/constants'; -import { CalloutDialog } from 'sql/workbench/browser/modal/calloutDialog'; +import { URI } from 'vs/base/common/uri'; +import { Modal, IDialogProperties } from 'sql/workbench/browser/modal/modal'; import { IFileDialogService, IOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IDialogProperties } from 'sql/workbench/browser/modal/modal'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -25,7 +25,7 @@ import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox'; import { RadioButton } from 'sql/base/browser/ui/radioButton/radioButton'; import { DialogPosition, DialogWidth } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { attachCalloutDialogStyler } from 'sql/workbench/common/styler'; import { escapeUrl } from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/utils'; export interface IImageCalloutDialogOptions { @@ -35,7 +35,9 @@ export interface IImageCalloutDialogOptions { embedImage?: boolean } -export class ImageCalloutDialog extends CalloutDialog { +const DEFAULT_DIALOG_WIDTH: DialogWidth = 452; + +export class ImageCalloutDialog extends Modal { private _selectionComplete: Deferred = new Deferred(); private _imageLocationLabel: HTMLElement; private _imageLocalRadioButton: RadioButton; @@ -49,9 +51,8 @@ export class ImageCalloutDialog extends CalloutDialog this.insert()); this.addFooterButton(constants.cancelButtonText, () => this.cancel(), undefined, true); this.registerListeners(); @@ -196,7 +205,7 @@ export class ImageCalloutDialog extends CalloutDialog { +const DEFAULT_DIALOG_WIDTH: DialogWidth = 452; + +export class LinkCalloutDialog extends Modal { private _selectionComplete: Deferred = new Deferred(); private _linkTextLabel: HTMLElement; private _linkTextInputBox: InputBox; @@ -42,8 +42,8 @@ export class LinkCalloutDialog extends CalloutDialog constructor( title: string, - dialogProperties: IDialogProperties, dialogPosition: DialogPosition, + dialogProperties: IDialogProperties, private readonly _defaultLabel: string = '', @IContextViewService private readonly _contextViewService: IContextViewService, @IThemeService themeService: IThemeService, @@ -56,16 +56,20 @@ export class LinkCalloutDialog extends CalloutDialog ) { super( title, - DEFAULT_DIALOG_WIDTH, - dialogProperties, - dialogPosition, - themeService, - layoutService, + TelemetryKeys.CalloutDialog, telemetryService, - contextKeyService, + layoutService, clipboardService, + themeService, logService, - textResourcePropertiesService + textResourcePropertiesService, + contextKeyService, + { + dialogStyle: 'callout', + dialogPosition: dialogPosition, + dialogProperties: dialogProperties, + width: DEFAULT_DIALOG_WIDTH + } ); let selection = window.getSelection(); if (selection.rangeCount > 0) { @@ -73,6 +77,9 @@ export class LinkCalloutDialog extends CalloutDialog } } + protected layout(height?: number): void { + } + /** * Opens the dialog and returns a promise for what options the user chooses. */ @@ -84,7 +91,8 @@ export class LinkCalloutDialog extends CalloutDialog public render(): void { super.render(); - attachModalDialogStyler(this, this._themeService); + attachCalloutDialogStyler(this, this._themeService); + this.addFooterButton(constants.insertButtonText, () => this.insert()); this.addFooterButton(constants.cancelButtonText, () => this.cancel(), undefined, true); this.registerListeners(); @@ -165,7 +173,7 @@ export class LinkCalloutDialog extends CalloutDialog } public cancel(): void { - super.cancel(); + this.hide('cancel'); this._selectionComplete.resolve({ insertEscapedMarkdown: '', insertUnescapedLinkLabel: escapeLabel(this._linkTextInputBox.value) diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts index 5fdaa78fa1..ff24707bd3 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts @@ -220,11 +220,12 @@ export class MarkdownToolbarComponent extends AngularDisposable { DOM.EventHelper.stop(event, true); let triggerElement = event.target as HTMLElement; let needsTransform = true; - let calloutResult: ILinkCalloutDialogOptions; + let linkCalloutResult: ILinkCalloutDialogOptions; + if (type === MarkdownButtonType.LINK_PREVIEW) { - calloutResult = await this.createCallout(type, triggerElement); + linkCalloutResult = await this.createCallout(type, triggerElement); // If no URL is present, no-op - if (!calloutResult.insertUnescapedLinkUrl) { + if (!linkCalloutResult.insertUnescapedLinkUrl) { return; } // If cell edit mode isn't WYSIWYG, use result from callout. No need for further transformation. @@ -234,15 +235,18 @@ export class MarkdownToolbarComponent extends AngularDisposable { // Otherwise, re-focus on the output element, and insert the link directly. this.output?.nativeElement?.focus(); // Callout is responsible for returning escaped strings - document.execCommand('insertHTML', false, `${calloutResult?.insertUnescapedLinkLabel}`); + document.execCommand('insertHTML', false, `${linkCalloutResult?.insertUnescapedLinkLabel}`); return; } } + const transformer = new MarkdownTextTransformer(this._notebookService, this.cellModel); if (needsTransform) { await transformer.transformText(type); } else if (!needsTransform) { - await insertFormattedMarkdown(calloutResult?.insertEscapedMarkdown, this.getCellEditorControl()); + if (type === MarkdownButtonType.LINK_PREVIEW) { + await insertFormattedMarkdown(linkCalloutResult?.insertEscapedMarkdown, this.getCellEditorControl()); + } } } @@ -283,7 +287,7 @@ export class MarkdownToolbarComponent extends AngularDisposable { if (type === MarkdownButtonType.LINK_PREVIEW) { const defaultLabel = this.getCurrentSelectionText(); - this._linkCallout = this._instantiationService.createInstance(LinkCalloutDialog, this.insertLinkHeading, dialogProperties, dialogPosition, defaultLabel); + this._linkCallout = this._instantiationService.createInstance(LinkCalloutDialog, this.insertLinkHeading, dialogPosition, dialogProperties, defaultLabel); this._linkCallout.render(); calloutOptions = await this._linkCallout.open(); } diff --git a/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts b/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts index 546df4440e..53954fb586 100644 --- a/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts +++ b/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts @@ -11,6 +11,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { Deferred } from 'sql/base/common/promise'; import { escapeLabel, escapeUrl } from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/utils'; +import { IDialogProperties } from 'sql/workbench/browser/modal/modal'; suite('Link Callout Dialog', function (): void { let layoutService: ILayoutService; @@ -18,6 +19,8 @@ suite('Link Callout Dialog', function (): void { let telemetryService: IAdsTelemetryService; let contextKeyService: IContextKeyService; + const defaultDialogProperties: IDialogProperties = { xPos: 0, yPos: 0, height: 250, width: 100 }; + setup(() => { layoutService = new TestLayoutService(); themeService = new TestThemeService(); @@ -26,7 +29,7 @@ suite('Link Callout Dialog', function (): void { }); test('Should return empty markdown on cancel', async function (): Promise { - let linkCalloutDialog = new LinkCalloutDialog('Title', undefined, 'below', 'defaultLabel', + let linkCalloutDialog = new LinkCalloutDialog('Title', 'below', defaultDialogProperties, 'defaultLabel', undefined, themeService, layoutService, telemetryService, contextKeyService, undefined, undefined, undefined); linkCalloutDialog.render(); @@ -47,7 +50,7 @@ suite('Link Callout Dialog', function (): void { test('Should return expected values on insert', async function (): Promise { const defaultLabel = 'defaultLabel'; const sampleUrl = 'https://www.aka.ms/azuredatastudio'; - let linkCalloutDialog = new LinkCalloutDialog('Title', undefined, 'below', defaultLabel, + let linkCalloutDialog = new LinkCalloutDialog('Title', 'below', defaultDialogProperties, defaultLabel, undefined, themeService, layoutService, telemetryService, contextKeyService, undefined, undefined, undefined); linkCalloutDialog.render(); @@ -70,7 +73,7 @@ suite('Link Callout Dialog', function (): void { test('Should return expected values on insert when escape necessary', async function (): Promise { const defaultLabel = 'default[]Label'; const sampleUrl = 'https://www.aka.ms/azuredatastudio()'; - let linkCalloutDialog = new LinkCalloutDialog('Title', undefined, 'below', defaultLabel, + let linkCalloutDialog = new LinkCalloutDialog('Title', 'below', defaultDialogProperties, defaultLabel, undefined, themeService, layoutService, telemetryService, contextKeyService, undefined, undefined, undefined); linkCalloutDialog.render(); diff --git a/src/sql/workbench/services/dialog/browser/customDialogService.ts b/src/sql/workbench/services/dialog/browser/customDialogService.ts index 03861a6d97..2a1a1219ce 100644 --- a/src/sql/workbench/services/dialog/browser/customDialogService.ts +++ b/src/sql/workbench/services/dialog/browser/customDialogService.ts @@ -22,8 +22,8 @@ export class CustomDialogService { let name = dialogName ? dialogName : 'CustomDialog'; if (options && (options.dialogStyle === 'callout')) { - options.positionX = document.activeElement.getBoundingClientRect().left; - options.positionY = document.activeElement.getBoundingClientRect().top; + options.dialogProperties.xPos = document.activeElement.getBoundingClientRect().left; + options.dialogProperties.yPos = document.activeElement.getBoundingClientRect().top; options.renderFooter = false; } let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, name, options || DefaultDialogOptions); diff --git a/src/sql/workbench/services/dialog/browser/dialogModal.ts b/src/sql/workbench/services/dialog/browser/dialogModal.ts index 47258ad33b..13c2745d4c 100644 --- a/src/sql/workbench/services/dialog/browser/dialogModal.ts +++ b/src/sql/workbench/services/dialog/browser/dialogModal.ts @@ -23,7 +23,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { attachCustomDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class DialogModal extends Modal { @@ -56,7 +56,7 @@ export class DialogModal extends Modal { public render() { super.render(); - attachModalDialogStyler(this, this._themeService); + attachCustomDialogStyler(this, this._themeService, this._modalOptions.dialogStyle); if (this._modalOptions.renderFooter !== false) { this._modalOptions.renderFooter = true;