mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-30 09:35:39 -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:
@@ -4,20 +4,118 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export enum BookTreeItemType {
|
||||
Book = 'Book',
|
||||
Notebook = 'Notebook',
|
||||
Markdown = 'Markdown',
|
||||
ExternalLink = 'ExternalLink'
|
||||
}
|
||||
|
||||
export interface BookTreeItemFormat {
|
||||
title: string;
|
||||
root: string;
|
||||
tableOfContents: any[];
|
||||
page: any;
|
||||
type: BookTreeItemType;
|
||||
}
|
||||
|
||||
export class BookTreeItem extends vscode.TreeItem {
|
||||
private _sections: any[];
|
||||
private _uri: string;
|
||||
private _previousUri: string;
|
||||
private _nextUri: string;
|
||||
public command: vscode.Command;
|
||||
|
||||
constructor(
|
||||
public readonly title: string,
|
||||
public readonly root: string,
|
||||
public readonly tableOfContents: any[],
|
||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
public uri?: string,
|
||||
public command?: vscode.Command
|
||||
) {
|
||||
super(title, collapsibleState);
|
||||
constructor(public book: BookTreeItemFormat) {
|
||||
super(book.title, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
|
||||
if (book.type === BookTreeItemType.Book) {
|
||||
this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
|
||||
this._sections = book.page;
|
||||
} else {
|
||||
this.setPageVariables();
|
||||
this.setCommand();
|
||||
}
|
||||
}
|
||||
|
||||
contextValue = 'book';
|
||||
private setPageVariables() {
|
||||
this.collapsibleState = (this.book.page.sections || this.book.page.subsections) && this.book.page.expand_sections ?
|
||||
vscode.TreeItemCollapsibleState.Expanded :
|
||||
this.book.page.sections || this.book.page.subsections ?
|
||||
vscode.TreeItemCollapsibleState.Collapsed :
|
||||
vscode.TreeItemCollapsibleState.None;
|
||||
this._sections = this.book.page.sections || this.book.page.subsections;
|
||||
this._uri = this.book.page.url;
|
||||
|
||||
let index = (this.book.tableOfContents.indexOf(this.book.page));
|
||||
this.setPreviousUri(index);
|
||||
this.setNextUri(index);
|
||||
}
|
||||
|
||||
private setCommand() {
|
||||
if (this.book.type === BookTreeItemType.Notebook) {
|
||||
let pathToNotebook = path.join(this.book.root, 'content', this._uri.concat('.ipynb'));
|
||||
this.command = { command: 'bookTreeView.openNotebook', title: localize('openNotebookCommand', 'Open Notebook'), arguments: [pathToNotebook], };
|
||||
} else if (this.book.type === BookTreeItemType.Markdown) {
|
||||
let pathToMarkdown = path.join(this.book.root, 'content', this._uri.concat('.md'));
|
||||
this.command = { command: 'bookTreeView.openMarkdown', title: localize('openMarkdownCommand', 'Open Markdown'), arguments: [pathToMarkdown], };
|
||||
} else if (this.book.type === BookTreeItemType.ExternalLink) {
|
||||
this.command = { command: 'bookTreeView.openExternalLink', title: localize('openExternalLinkCommand', 'Open External Link'), arguments: [this._uri], };
|
||||
}
|
||||
}
|
||||
|
||||
private setPreviousUri(index: number): void {
|
||||
let i = --index;
|
||||
while (i > -1) {
|
||||
if (this.book.tableOfContents[i].url) {
|
||||
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
|
||||
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents[i].url.concat('.ipynb'));
|
||||
if (fs.existsSync(pathToNotebook)) {
|
||||
this._previousUri = pathToNotebook;
|
||||
return;
|
||||
}
|
||||
}
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
private setNextUri(index: number): void {
|
||||
let i = ++index;
|
||||
while (i < this.book.tableOfContents.length) {
|
||||
if (this.book.tableOfContents[i].url) {
|
||||
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
|
||||
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents[i].url.concat('.ipynb'));
|
||||
if (fs.existsSync(pathToNotebook)) {
|
||||
this._nextUri = pathToNotebook;
|
||||
return;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public get root(): string {
|
||||
return this.book.root;
|
||||
}
|
||||
|
||||
public get tableOfContents(): any[] {
|
||||
return this.book.tableOfContents;
|
||||
}
|
||||
|
||||
public get sections(): any[] {
|
||||
return this._sections;
|
||||
}
|
||||
|
||||
public get previousUri(): string {
|
||||
return this._previousUri;
|
||||
}
|
||||
|
||||
public get nextUri(): string {
|
||||
return this._nextUri;
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,23 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { BookTreeItem } from './bookTreeItem';
|
||||
import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem> {
|
||||
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem>, azdata.nb.NavigationProvider {
|
||||
readonly providerId: string = 'BookNavigator';
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<BookTreeItem | undefined> = new vscode.EventEmitter<BookTreeItem | undefined>();
|
||||
readonly onDidChangeTreeData: vscode.Event<BookTreeItem | undefined> = this._onDidChangeTreeData.event;
|
||||
private _tableOfContentsPath: string[];
|
||||
private _allNotebooks = new Map<string, BookTreeItem>();
|
||||
|
||||
constructor(private workspaceRoot: string) {
|
||||
if (workspaceRoot !== '') {
|
||||
@@ -71,15 +74,14 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getTreeItem(element: BookTreeItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getChildren(element?: BookTreeItem): Thenable<BookTreeItem[]> {
|
||||
if (element) {
|
||||
if (element.tableOfContents) {
|
||||
return Promise.resolve(this.getSections(element.tableOfContents, element.root));
|
||||
if (element.sections) {
|
||||
return Promise.resolve(this.getSections(element.tableOfContents, element.sections, element.root));
|
||||
} else {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
@@ -88,6 +90,10 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
}
|
||||
}
|
||||
|
||||
private flattenArray(array: any[]): any[] {
|
||||
return array.reduce((acc, val) => Array.isArray(val.sections) ? acc.concat(val).concat(this.flattenArray(val.sections)) : acc.concat(val), []);
|
||||
}
|
||||
|
||||
private getBooks(): BookTreeItem[] {
|
||||
let books: BookTreeItem[] = [];
|
||||
for (let i in this._tableOfContentsPath) {
|
||||
@@ -95,7 +101,13 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
try {
|
||||
const config = yaml.safeLoad(fs.readFileSync(path.join(root, '_config.yml'), 'utf-8'));
|
||||
const tableOfContents = yaml.safeLoad(fs.readFileSync(this._tableOfContentsPath[i], 'utf-8'));
|
||||
let book = new BookTreeItem(config.title, root, tableOfContents, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
let book = new BookTreeItem({
|
||||
title: config.title,
|
||||
root: root,
|
||||
tableOfContents: this.flattenArray(tableOfContents),
|
||||
page: tableOfContents,
|
||||
type: BookTreeItemType.Book
|
||||
});
|
||||
books.push(book);
|
||||
} catch (e) {
|
||||
vscode.window.showErrorMessage(localize('openConfigFileError', 'Open file {0} failed: {1}',
|
||||
@@ -106,30 +118,45 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
return books;
|
||||
}
|
||||
|
||||
private getSections(sec: any[], root: string): BookTreeItem[] {
|
||||
private getSections(tableOfContents: any[], sections: any[], root: string): BookTreeItem[] {
|
||||
let notebooks: BookTreeItem[] = [];
|
||||
for (let i = 0; i < sec.length; i++) {
|
||||
if (sec[i].url) {
|
||||
if (sec[i].external) {
|
||||
let externalLink = new BookTreeItem(sec[i].title, root, sec[i].sections, sec[i].sections ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None, sec[i].url, { command: 'bookTreeView.openExternalLink', title: localize('openExternalLinkCommand', 'Open External Link'), arguments: [sec[i].url], });
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
if (sections[i].url) {
|
||||
if (sections[i].external) {
|
||||
let externalLink = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
root: root,
|
||||
tableOfContents: tableOfContents,
|
||||
page: sections[i],
|
||||
type: BookTreeItemType.ExternalLink
|
||||
});
|
||||
notebooks.push(externalLink);
|
||||
} else {
|
||||
let pathToNotebook = path.join(root, 'content', sec[i].url.concat('.ipynb'));
|
||||
let pathToMarkdown = path.join(root, 'content', sec[i].url.concat('.md'));
|
||||
let pathToNotebook = path.join(root, 'content', sections[i].url.concat('.ipynb'));
|
||||
let pathToMarkdown = path.join(root, 'content', sections[i].url.concat('.md'));
|
||||
// Note: Currently, if there is an ipynb and a md file with the same name, Jupyter Books only shows the notebook.
|
||||
// Following Jupyter Books behavior for now
|
||||
if (fs.existsSync(pathToNotebook)) {
|
||||
let notebook = new BookTreeItem(sec[i].title, root, sec[i].sections || sec[i].subsections,
|
||||
(sec[i].sections || sec[i].subsections) && sec[i].expand_sections ? vscode.TreeItemCollapsibleState.Expanded : sec[i].sections || sec[i].subsections ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None,
|
||||
sec[i].url, { command: 'bookTreeView.openNotebook', title: localize('openNotebookCommand', 'Open Notebook'), arguments: [pathToNotebook], });
|
||||
let notebook = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
root: root,
|
||||
tableOfContents: tableOfContents,
|
||||
page: sections[i],
|
||||
type: BookTreeItemType.Notebook
|
||||
});
|
||||
notebooks.push(notebook);
|
||||
this._allNotebooks.set(pathToNotebook, notebook);
|
||||
} else if (fs.existsSync(pathToMarkdown)) {
|
||||
let markdown = new BookTreeItem(sec[i].title, root, sec[i].sections || sec[i].subsections,
|
||||
(sec[i].sections || sec[i].subsections) && sec[i].expand_sections ? vscode.TreeItemCollapsibleState.Expanded : sec[i].sections || sec[i].subsections ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None,
|
||||
sec[i].url, { command: 'bookTreeView.openMarkdown', title: localize('openMarkdownCommand', 'Open Markdown'), arguments: [pathToMarkdown], });
|
||||
let markdown = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
root: root,
|
||||
tableOfContents: tableOfContents,
|
||||
page: sections[i],
|
||||
type: BookTreeItemType.Markdown
|
||||
});
|
||||
notebooks.push(markdown);
|
||||
} else {
|
||||
vscode.window.showErrorMessage(localize('missingFileError', 'Missing file : {0}', sec[i].title));
|
||||
vscode.window.showErrorMessage(localize('missingFileError', 'Missing file : {0}', sections[i].title));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -138,4 +165,24 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
}
|
||||
return notebooks;
|
||||
}
|
||||
|
||||
getNavigation(uri: vscode.Uri): Thenable<azdata.nb.NavigationResult> {
|
||||
let notebook = this._allNotebooks.get(uri.fsPath);
|
||||
let result: azdata.nb.NavigationResult;
|
||||
if (notebook) {
|
||||
result = {
|
||||
hasNavigation: true,
|
||||
previous: notebook.previousUri ? vscode.Uri.file(notebook.previousUri) : undefined,
|
||||
next: notebook.nextUri ? vscode.Uri.file(notebook.nextUri) : undefined
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
hasNavigation: false,
|
||||
previous: undefined,
|
||||
next: undefined
|
||||
};
|
||||
}
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,10 +29,11 @@ type ChooseCellType = { label: string, id: CellType };
|
||||
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
||||
|
||||
const bookTreeViewProvider = new BookTreeViewProvider(vscode.workspace.rootPath || '');
|
||||
vscode.window.registerTreeDataProvider('bookTreeView', bookTreeViewProvider);
|
||||
vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource));
|
||||
vscode.commands.registerCommand('bookTreeView.openMarkdown', (resource) => bookTreeViewProvider.openMarkdown(resource));
|
||||
vscode.commands.registerCommand('bookTreeView.openExternalLink', (resource) => bookTreeViewProvider.openExternalLink(resource));
|
||||
extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider('bookTreeView', bookTreeViewProvider));
|
||||
extensionContext.subscriptions.push(azdata.nb.registerNavigationProvider(bookTreeViewProvider));
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource)));
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openMarkdown', (resource) => bookTreeViewProvider.openMarkdown(resource)));
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openExternalLink', (resource) => bookTreeViewProvider.openExternalLink(resource)));
|
||||
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('_notebook.command.new', (context?: azdata.ConnectedContext) => {
|
||||
let connectionProfile: azdata.IConnectionProfile = undefined;
|
||||
|
||||
Reference in New Issue
Block a user