From be45905830020161f7ca58152c5a6e3df5465633 Mon Sep 17 00:00:00 2001 From: Matt Irvine Date: Thu, 12 Jul 2018 11:15:56 -0700 Subject: [PATCH] Add wizard sidebar navigation (#1911) --- src/sql/platform/dialog/dialog.module.ts | 7 +- src/sql/platform/dialog/media/dialogModal.css | 2 +- .../dialog/media/wizardNavigation.css | 94 +++++++++++++++++++ src/sql/platform/dialog/wizardModal.ts | 25 ++++- .../dialog/wizardNavigation.component.ts | 91 ++++++++++++++++++ 5 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 src/sql/platform/dialog/media/wizardNavigation.css create mode 100644 src/sql/platform/dialog/wizardNavigation.component.ts diff --git a/src/sql/platform/dialog/dialog.module.ts b/src/sql/platform/dialog/dialog.module.ts index d78e91a7d0..9e66776e44 100644 --- a/src/sql/platform/dialog/dialog.module.ts +++ b/src/sql/platform/dialog/dialog.module.ts @@ -13,6 +13,7 @@ import { CommonModule, APP_BASE_HREF } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { DialogContainer } from 'sql/platform/dialog/dialogContainer.component'; +import { WizardNavigation } from 'sql/platform/dialog/wizardNavigation.component'; import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry'; import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component'; import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component'; @@ -37,12 +38,13 @@ export const DialogModule = (params, selector: string, instantiationService: IIn SelectBox, InputBox, DialogContainer, + WizardNavigation, ModelViewContent, ModelComponentWrapper, ComponentHostDirective, ...extensionComponents ], - entryComponents: [DialogContainer, ...extensionComponents], + entryComponents: [DialogContainer, WizardNavigation, ...extensionComponents], imports: [ FormsModule, CommonModule, @@ -65,7 +67,8 @@ export const DialogModule = (params, selector: string, instantiationService: IIn } ngDoBootstrap(appRef: ApplicationRef) { - const factoryWrapper: any = this._resolver.resolveComponentFactory(DialogContainer); + let componentClass = this.selector.startsWith(WizardNavigation.SELECTOR) ? WizardNavigation : DialogContainer; + const factoryWrapper: any = this._resolver.resolveComponentFactory(componentClass); factoryWrapper.factory.selector = this.selector; appRef.bootstrap(factoryWrapper); } diff --git a/src/sql/platform/dialog/media/dialogModal.css b/src/sql/platform/dialog/media/dialogModal.css index d8dc42b2ed..0d5dc54bdf 100644 --- a/src/sql/platform/dialog/media/dialogModal.css +++ b/src/sql/platform/dialog/media/dialogModal.css @@ -5,7 +5,7 @@ .dialogModal-body { display: flex; - flex-direction: column; + flex-direction: row; height: 100%; width: 100%; min-width: 500px; diff --git a/src/sql/platform/dialog/media/wizardNavigation.css b/src/sql/platform/dialog/media/wizardNavigation.css new file mode 100644 index 0000000000..c8c4440642 --- /dev/null +++ b/src/sql/platform/dialog/media/wizardNavigation.css @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.wizardNavigation-container { + display: flex; + flex-direction: column; + width: 80px; + height: calc(100% + 25px); + margin-top: -25px; +} + +.hc-black .wizardNavigation-container { + border-right-color: #2b56f2; + border-right-style: solid; + border-right-width: 1px; + background-color: unset; +} + +.wizardNavigation-pageNumber { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + max-height: 130px; +} + +.wizardNavigation-pageNumber a { + text-decoration: none; +} + +.wizardNavigation-dot { + height: 30px; + width: 30px; + background-color: rgb(200, 200, 200); + color: white; + border-radius: 50%; + border-style: none; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; +} + +.hc-black .wizardNavigation-dot { + flex-grow: 1; + background-color: unset; + border-style: solid; + border-width: 1px; + border-color: white; +} + +.wizardNavigation-connector { + width: 3px; + display: inline-block; + flex-grow: 1; + background-color: rgb(200, 200, 200); +} + +.hc-black .wizardNavigation-connector { + display: none; +} + +.wizardNavigation-connector.active, +.wizardNavigation-dot.active { + background-color: rgb(9, 109, 201); +} + +.hc-black .wizardNavigation-dot.active { + border-color: #2b56f2; + background-color: unset; + border-style: solid; +} + +.hc-black .wizardNavigation-dot.active:hover, +.hc-black .wizardNavigation-dot.active.currentPage:hover { + border-color: #F38518; + border-style: dashed; +} + +.wizardNavigation-dot.active.currentPage { + border-style: double; +} + +.hc-black .wizardNavigation-dot.active.currentPage { + border-style: solid; + border-color: #F38518; +} + +.wizardNavigation-connector.invisible { + visibility: hidden; +} \ No newline at end of file diff --git a/src/sql/platform/dialog/wizardModal.ts b/src/sql/platform/dialog/wizardModal.ts index b71f11cb6e..0cc519fb4a 100644 --- a/src/sql/platform/dialog/wizardModal.ts +++ b/src/sql/platform/dialog/wizardModal.ts @@ -8,23 +8,22 @@ import 'vs/css!./media/dialogModal'; import { Modal, IModalOptions } from 'sql/base/browser/ui/modal/modal'; import { attachModalDialogStyler } from 'sql/common/theme/styler'; -import { Wizard, Dialog, DialogButton, WizardPage } from 'sql/platform/dialog/dialogTypes'; +import { Wizard, DialogButton, WizardPage } from 'sql/platform/dialog/dialogTypes'; import { DialogPane } from 'sql/platform/dialog/dialogPane'; import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; +import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { DialogModule } from 'sql/platform/dialog/dialog.module'; import { Button } from 'vs/base/browser/ui/button/button'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { Builder } from 'vs/base/browser/builder'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; -import { localize } from 'vs/nls'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { DialogMessage, MessageLevel } from '../../workbench/api/common/sqlExtHostTypes'; export class WizardModal extends Modal { private _dialogPanes = new Map(); @@ -128,6 +127,8 @@ export class WizardModal extends Modal { this._body = bodyBuilder.getHTMLElement(); }); + this.initializeNavigation(this._body); + this._wizard.pages.forEach(page => { this.registerPage(page); }); @@ -201,6 +202,22 @@ export class WizardModal extends Modal { } } + /** + * Bootstrap angular for the wizard's left nav bar + */ + private initializeNavigation(bodyContainer: HTMLElement) { + bootstrapAngular(this._instantiationService, + DialogModule, + bodyContainer, + 'wizard-navigation', + { + wizard: this._wizard, + navigationHandler: (index: number) => this.showPage(index, index > this._wizard.currentPage) + }, + undefined, + () => undefined); + } + public open(): void { this.showPage(0, false); this.show(); diff --git a/src/sql/platform/dialog/wizardNavigation.component.ts b/src/sql/platform/dialog/wizardNavigation.component.ts new file mode 100644 index 0000000000..8303843597 --- /dev/null +++ b/src/sql/platform/dialog/wizardNavigation.component.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Source EULA. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import 'vs/css!./media/wizardNavigation'; +import { Component, Inject, forwardRef, ElementRef, AfterViewInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; +import { Event, Emitter } from 'vs/base/common/event'; +import { Wizard } from './dialogTypes'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { attachStyler } from 'vs/platform/theme/common/styler'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; + +export class WizardNavigationParams implements IBootstrapParams { + wizard: Wizard; + navigationHandler: (index: number) => void; +} + +@Component({ + selector: WizardNavigation.SELECTOR, + providers: [], + template: ` +
+ + + +
+ ` +}) +export class WizardNavigation implements AfterViewInit { + public static readonly SELECTOR = 'wizard-navigation'; + + private _onResize = new Emitter(); + public readonly onResize: Event = this._onResize.event; + + @ViewChild('container', { read: ElementRef }) private _container: ElementRef; + constructor( + @Inject(forwardRef(() => ElementRef)) private _el: ElementRef, + @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, + @Inject(IBootstrapParams) private _params: WizardNavigationParams, + @Inject(IWorkbenchThemeService) private _themeService: IWorkbenchThemeService) { + } + + ngAfterViewInit() { + this._themeService.onThemeChange(() => this.style()); + this.style(); + this._params.wizard.onPageChanged(() => this._changeRef.detectChanges()); + } + + hasTopConnector(index: number): boolean { + return index > 0; + } + + hasBottomConnector(index: number): boolean { + return index + 1 !== this._params.wizard.pages.length; + } + + isActive(index: number): boolean { + return index <= this._params.wizard.currentPage; + } + + isCurrentPage(index: number): boolean { + return index === this._params.wizard.currentPage; + } + + navigate(index: number): void { + if (this.isActive(index)) { + this._params.navigationHandler(index); + } + } + + private style(): void { + let theme = this._themeService.getTheme(); + let navigationBackgroundColor = theme.getColor(SIDE_BAR_BACKGROUND); + if (theme.type === 'light') { + navigationBackgroundColor = navigationBackgroundColor.lighten(0.03); + } else if (theme.type === 'dark') { + navigationBackgroundColor = navigationBackgroundColor.darken(0.03); + } + (this._container.nativeElement as HTMLElement).style.backgroundColor = navigationBackgroundColor.toString(); + } +}