mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 01:25:36 -05:00
Books/navigation (#6280)
* added previous and next buttons * previous and next notebook API * links for prev/next notebooks * fixed first and last pages * made code more readable * addressed Kevin's comments * moved logic over to BookTreeItem * show buttons in dev mode only * added BookTreeItemFormat interface * added interface and enum * removed localize call
This commit is contained in:
13
src/sql/azdata.proposed.d.ts
vendored
13
src/sql/azdata.proposed.d.ts
vendored
@@ -5255,6 +5255,19 @@ declare module 'azdata' {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function registerNavigationProvider(provider: NavigationProvider): vscode.Disposable;
|
||||
|
||||
export interface NavigationProvider {
|
||||
readonly providerId: string;
|
||||
getNavigation(notebookUri: vscode.Uri): Thenable<NavigationResult>;
|
||||
}
|
||||
|
||||
export interface NavigationResult {
|
||||
hasNavigation: boolean;
|
||||
previous?: vscode.Uri;
|
||||
next?: vscode.Uri;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Disposable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { IMainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ok } from 'vs/base/common/assert';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
import {
|
||||
SqlMainContext, INotebookDocumentsAndEditorsDelta, ExtHostNotebookDocumentsAndEditorsShape,
|
||||
@@ -21,10 +22,13 @@ import {
|
||||
import { ExtHostNotebookDocumentData } from 'sql/workbench/api/node/extHostNotebookDocumentData';
|
||||
import { ExtHostNotebookEditor } from 'sql/workbench/api/node/extHostNotebookEditor';
|
||||
|
||||
type Adapter = azdata.nb.NavigationProvider;
|
||||
|
||||
export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocumentsAndEditorsShape {
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private _disposables: Disposable[] = [];
|
||||
private _adapters = new Map<number, Adapter>();
|
||||
|
||||
private _activeEditorId: string;
|
||||
private _proxy: MainThreadNotebookDocumentsAndEditorsShape;
|
||||
@@ -152,6 +156,33 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
|
||||
}
|
||||
}
|
||||
|
||||
private _nextHandle(): number {
|
||||
return ExtHostNotebookDocumentsAndEditors._handlePool++;
|
||||
}
|
||||
|
||||
private _addNewAdapter(adapter: Adapter): number {
|
||||
const handle = this._nextHandle();
|
||||
this._adapters.set(handle, adapter);
|
||||
return handle;
|
||||
}
|
||||
|
||||
private _getAdapter<T>(id: number): T {
|
||||
let adapter = <T><any>this._adapters.get(id);
|
||||
if (adapter === undefined) {
|
||||
throw new Error('No adapter found');
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
$getNavigation(handle: number, notebookUri: UriComponents): Thenable<azdata.nb.NavigationResult> {
|
||||
let navProvider = this._getAdapter<azdata.nb.NavigationProvider>(handle);
|
||||
if (navProvider) {
|
||||
let uri = URI.revive(notebookUri);
|
||||
return navProvider.getNavigation(uri);
|
||||
}
|
||||
throw new Error('No navigation provider found for handle ${handle}');
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Extension accessible methods
|
||||
@@ -212,5 +243,17 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
|
||||
this._editors.forEach(data => result.push(data));
|
||||
return result;
|
||||
}
|
||||
|
||||
registerNavigationProvider(provider: azdata.nb.NavigationProvider): vscode.Disposable {
|
||||
if (!provider || !provider.providerId) {
|
||||
throw new Error(localize('providerRequired', 'A NotebookProvider with valid providerId must be passed to this method'));
|
||||
}
|
||||
const handle = this._addNewAdapter(provider);
|
||||
this._proxy.$registerNavigationProvider(provider.providerId, handle);
|
||||
return new Disposable(() => {
|
||||
this._adapters.delete(handle);
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@@ -327,7 +327,8 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IEditorService private _editorService: IEditorService,
|
||||
@IEditorGroupsService private _editorGroupService: IEditorGroupsService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
@INotebookService private readonly _notebookService: INotebookService
|
||||
) {
|
||||
super();
|
||||
if (extHostContext) {
|
||||
@@ -685,4 +686,22 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
||||
this._modelToDisposeMap.set(editor.id, listeners);
|
||||
});
|
||||
}
|
||||
|
||||
$registerNavigationProvider(providerId: string, handle: number): void {
|
||||
this._notebookService.registerNavigationProvider({
|
||||
providerId: providerId,
|
||||
onNext: async (uri) => {
|
||||
let result = await this._proxy.$getNavigation(handle, uri);
|
||||
if (result) {
|
||||
this.doOpenEditor(result.next, {});
|
||||
}
|
||||
},
|
||||
onPrevious: async (uri) => {
|
||||
let result = await this._proxy.$getNavigation(handle, uri);
|
||||
if (result) {
|
||||
this.doOpenEditor(result.previous, {});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,6 +501,9 @@ export function createApiFactory(
|
||||
registerNotebookProvider(provider: azdata.nb.NotebookProvider): vscode.Disposable {
|
||||
return extHostNotebook.registerNotebookProvider(provider);
|
||||
},
|
||||
registerNavigationProvider(provider: azdata.nb.NavigationProvider): vscode.Disposable {
|
||||
return extHostNotebookDocumentsAndEditors.registerNavigationProvider(provider);
|
||||
},
|
||||
CellRange: sqlExtHostTypes.CellRange,
|
||||
NotebookChangeKind: sqlExtHostTypes.NotebookChangeKind
|
||||
};
|
||||
|
||||
@@ -913,6 +913,7 @@ export interface INotebookShowOptions {
|
||||
export interface ExtHostNotebookDocumentsAndEditorsShape {
|
||||
$acceptDocumentsAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
|
||||
$acceptModelChanged(strURL: UriComponents, e: INotebookModelChangedData);
|
||||
$getNavigation(handle: number, uri: vscode.Uri): Thenable<azdata.nb.NavigationResult>;
|
||||
}
|
||||
|
||||
export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {
|
||||
@@ -924,6 +925,7 @@ export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable
|
||||
$clearOutput(id: string, cellUri: UriComponents): Promise<boolean>;
|
||||
$clearAllOutputs(id: string): Promise<boolean>;
|
||||
$changeKernel(id: string, kernel: azdata.nb.IKernelInfo): Promise<boolean>;
|
||||
$registerNavigationProvider(providerId: string, handle: number);
|
||||
}
|
||||
|
||||
export interface ExtHostExtensionManagementShape {
|
||||
|
||||
@@ -19,5 +19,7 @@
|
||||
<placeholder-cell-component [cellModel]="cell" [model]="model">
|
||||
</placeholder-cell-component>
|
||||
</div>
|
||||
<div class="book-nav" #bookNav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { nb } from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy } from '@angular/core';
|
||||
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
@@ -19,6 +20,7 @@ import { IAction, Action, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
|
||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||
import { CellTypes, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
@@ -50,6 +52,8 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { LabeledMenuItemActionItem, fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
|
||||
|
||||
@@ -63,6 +67,8 @@ export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
||||
export class NotebookComponent extends AngularDisposable implements OnInit, OnDestroy, INotebookEditor {
|
||||
@ViewChild('toolbar', { read: ElementRef }) private toolbar: ElementRef;
|
||||
@ViewChild('container', { read: ElementRef }) private container: ElementRef;
|
||||
@ViewChild('bookNav', { read: ElementRef }) private bookNav: ElementRef;
|
||||
|
||||
private _model: NotebookModel;
|
||||
private _isInErrorState: boolean = false;
|
||||
private _errorMessage: string;
|
||||
@@ -127,6 +133,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||
this.updateTheme(this.themeService.getColorTheme());
|
||||
this.initActionBar();
|
||||
if (this.contextKeyService.getContextKeyValue('isDevelopment') &&
|
||||
this.contextKeyService.getContextKeyValue('bookOpened')) {
|
||||
this.initNavSection();
|
||||
}
|
||||
this.setScrollPosition();
|
||||
this.doLoad();
|
||||
}
|
||||
@@ -392,7 +402,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
this.notificationService.error(error);
|
||||
}
|
||||
|
||||
protected initActionBar() {
|
||||
protected initActionBar(): void {
|
||||
let kernelContainer = document.createElement('div');
|
||||
let kernelDropdown = new KernelsDropdown(kernelContainer, this.contextViewService, this.modelReady);
|
||||
kernelDropdown.render(kernelContainer);
|
||||
@@ -428,6 +438,24 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
{ action: this._runAllCellsAction },
|
||||
{ action: clearResultsButton }
|
||||
]);
|
||||
}
|
||||
|
||||
protected initNavSection(): void {
|
||||
this.addButton(localize('previousButtonLabel', "Previous"),
|
||||
() => this.previousPage());
|
||||
this.addButton(localize('nextButtonLabel', "Next"),
|
||||
() => this.nextPage());
|
||||
}
|
||||
|
||||
private addButton(label: string, onDidClick?: () => void): void {
|
||||
const container = DOM.append(this.bookNav.nativeElement, DOM.$('.dialog-message-button'));
|
||||
let button = new Button(container);
|
||||
button.icon = '';
|
||||
button.label = label;
|
||||
if (onDidClick) {
|
||||
this._register(button.onDidClick(onDidClick));
|
||||
}
|
||||
this._register(attachButtonStyler(button, this.themeService));
|
||||
|
||||
}
|
||||
|
||||
@@ -582,6 +610,28 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
}
|
||||
}
|
||||
|
||||
public async nextPage(): Promise<void> {
|
||||
try {
|
||||
let navProvider = this.notebookService.getNavigationProvider(this.model.notebookUri);
|
||||
if (navProvider) {
|
||||
navProvider.onNext(this.model.notebookUri);
|
||||
}
|
||||
} catch (error) {
|
||||
this.notificationService.error(notebookUtils.getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
public previousPage() {
|
||||
try {
|
||||
let navProvider = this.notebookService.getNavigationProvider(this.model.notebookUri);
|
||||
if (navProvider) {
|
||||
navProvider.onPrevious(this.model.notebookUri);
|
||||
}
|
||||
} catch (error) {
|
||||
this.notificationService.error(notebookUtils.getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
getSections(): INotebookSection[] {
|
||||
return this.getSectionElements();
|
||||
}
|
||||
|
||||
@@ -125,3 +125,15 @@
|
||||
.hc-black .monaco-workbench .notebook-action.new-notebook {
|
||||
background: url('./media/dark/new_notebook_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.notebookEditor .book-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.notebookEditor .book-nav .dialog-message-button {
|
||||
min-width: 60px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@@ -45,6 +45,10 @@ export interface INotebookService {
|
||||
*/
|
||||
unregisterProvider(providerId: string): void;
|
||||
|
||||
registerNavigationProvider(provider: INavigationProvider): void;
|
||||
|
||||
getNavigationProvider(notebookUri: URI): INavigationProvider;
|
||||
|
||||
getSupportedFileExtensions(): string[];
|
||||
|
||||
getProvidersForFileType(fileType: string): string[];
|
||||
@@ -149,3 +153,9 @@ export interface INotebookEditor {
|
||||
getSections(): INotebookSection[];
|
||||
navigateToSection(sectionId: string): void;
|
||||
}
|
||||
|
||||
export interface INavigationProvider {
|
||||
providerId: string;
|
||||
onNext(uri: URI): void;
|
||||
onPrevious(uri: URI): void;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
import {
|
||||
INotebookService, INotebookManager, INotebookProvider,
|
||||
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor, SQL_NOTEBOOK_PROVIDER, OVERRIDE_EDITOR_THEMING_SETTING
|
||||
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor, SQL_NOTEBOOK_PROVIDER, OVERRIDE_EDITOR_THEMING_SETTING, INavigationProvider
|
||||
} from 'sql/workbench/services/notebook/common/notebookService';
|
||||
import { RenderMimeRegistry } from 'sql/workbench/parts/notebook/outputs/registry';
|
||||
import { standardRendererFactories } from 'sql/workbench/parts/notebook/outputs/factories';
|
||||
@@ -97,6 +97,7 @@ export class NotebookService extends Disposable implements INotebookService {
|
||||
private _trustedNotebooksMemento: Memento;
|
||||
private _mimeRegistry: RenderMimeRegistry;
|
||||
private _providers: Map<string, ProviderDescriptor> = new Map();
|
||||
private _navigationProviders: Map<string, INavigationProvider> = new Map();
|
||||
private _managersMap: Map<string, INotebookManager[]> = new Map();
|
||||
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
|
||||
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
|
||||
@@ -269,6 +270,15 @@ export class NotebookService extends Disposable implements INotebookService {
|
||||
this._providers.delete(providerId);
|
||||
}
|
||||
|
||||
registerNavigationProvider(provider: INavigationProvider): void {
|
||||
this._navigationProviders.set(provider.providerId, provider);
|
||||
}
|
||||
|
||||
getNavigationProvider(): INavigationProvider {
|
||||
let provider = this._navigationProviders.size > 0 ? this._navigationProviders.values().next().value : undefined;
|
||||
return provider;
|
||||
}
|
||||
|
||||
get isRegistrationComplete(): boolean {
|
||||
return this._isRegistrationComplete;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user