mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Pinning Notebooks on Notebooks view (#11963)
* initial commit * added tests * code cleanup and more tests * add missed util test * changes to address comments * remove pin from resources
This commit is contained in:
89
extensions/notebook/src/book/bookPinManager.ts
Normal file
89
extensions/notebook/src/book/bookPinManager.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as constants from './../common/constants';
|
||||
import { BookTreeItem } from './bookTreeItem';
|
||||
import { getPinnedNotebooks } from '../common/utils';
|
||||
|
||||
export interface IBookPinManager {
|
||||
pinNotebook(notebook: BookTreeItem): boolean;
|
||||
unpinNotebook(notebook: BookTreeItem): boolean;
|
||||
}
|
||||
|
||||
enum PinBookOperation {
|
||||
Pin,
|
||||
Unpin
|
||||
}
|
||||
|
||||
export class BookPinManager implements IBookPinManager {
|
||||
|
||||
constructor() {
|
||||
this.setPinnedSectionContext();
|
||||
}
|
||||
|
||||
setPinnedSectionContext(): void {
|
||||
if (getPinnedNotebooks().length > 0) {
|
||||
vscode.commands.executeCommand(constants.BuiltInCommands.SetContext, constants.showPinnedBooksContextKey, true);
|
||||
} else {
|
||||
vscode.commands.executeCommand(constants.BuiltInCommands.SetContext, constants.showPinnedBooksContextKey, false);
|
||||
}
|
||||
}
|
||||
|
||||
isNotebookPinned(notebookPath: string): boolean {
|
||||
if (getPinnedNotebooks().findIndex(x => x === notebookPath) > -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pinNotebook(notebook: BookTreeItem): boolean {
|
||||
return this.isNotebookPinned(notebook.book.contentPath) ? false : this.updatePinnedBooks(notebook, PinBookOperation.Pin);
|
||||
}
|
||||
|
||||
unpinNotebook(notebook: BookTreeItem): boolean {
|
||||
return this.updatePinnedBooks(notebook, PinBookOperation.Unpin);
|
||||
}
|
||||
|
||||
getPinnedBookPathsInConfig(): string[] {
|
||||
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(constants.notebookConfigKey);
|
||||
let pinnedBookDirectories: string[] = config.get(constants.pinnedBooksConfigKey);
|
||||
|
||||
return pinnedBookDirectories;
|
||||
}
|
||||
|
||||
setPinnedBookPathsInConfig(bookPaths: string[]) {
|
||||
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(constants.notebookConfigKey);
|
||||
let storeInWorspace: boolean = this.hasWorkspaceFolders();
|
||||
|
||||
config.update(constants.pinnedBooksConfigKey, bookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
hasWorkspaceFolders(): boolean {
|
||||
let workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
return workspaceFolders && workspaceFolders.length > 0;
|
||||
}
|
||||
|
||||
updatePinnedBooks(notebook: BookTreeItem, operation: PinBookOperation) {
|
||||
let modifiedPinnedBooks = false;
|
||||
let bookPathToChange: string = notebook.book.contentPath;
|
||||
|
||||
let pinnedBooks: string[] = this.getPinnedBookPathsInConfig();
|
||||
let existingBookIndex = pinnedBooks.map(pinnedBookPath => path.normalize(pinnedBookPath)).indexOf(bookPathToChange);
|
||||
|
||||
if (existingBookIndex !== -1 && operation === PinBookOperation.Unpin) {
|
||||
pinnedBooks.splice(existingBookIndex, 1);
|
||||
modifiedPinnedBooks = true;
|
||||
} else if (existingBookIndex === -1 && operation === PinBookOperation.Pin) {
|
||||
pinnedBooks.push(bookPathToChange);
|
||||
modifiedPinnedBooks = true;
|
||||
}
|
||||
|
||||
this.setPinnedBookPathsInConfig(pinnedBooks);
|
||||
this.setPinnedSectionContext();
|
||||
|
||||
return modifiedPinnedBooks;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { IJupyterBookSection, IJupyterBookToc } from '../contracts/content';
|
||||
import * as loc from '../common/localizedConstants';
|
||||
import { isBookItemPinned } from '../common/utils';
|
||||
|
||||
export enum BookTreeItemType {
|
||||
Book = 'Book',
|
||||
@@ -54,6 +55,8 @@ export class BookTreeItem extends vscode.TreeItem {
|
||||
} else {
|
||||
this.contextValue = 'savedNotebook';
|
||||
}
|
||||
} else {
|
||||
this.contextValue = book.type === BookTreeItemType.Notebook ? (isBookItemPinned(book.contentPath) ? 'pinnedNotebook' : 'savedNotebook') : 'section';
|
||||
}
|
||||
this.setPageVariables();
|
||||
this.setCommand();
|
||||
|
||||
@@ -17,7 +17,8 @@ import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
|
||||
import * as loc from '../common/localizedConstants';
|
||||
import * as glob from 'fast-glob';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { debounce } from '../common/utils';
|
||||
import { debounce, getPinnedNotebooks } from '../common/utils';
|
||||
import { IBookPinManager, BookPinManager } from './bookPinManager';
|
||||
|
||||
const Content = 'content';
|
||||
|
||||
@@ -34,6 +35,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
private _initializeDeferred: Deferred<void> = new Deferred<void>();
|
||||
private _openAsUntitled: boolean;
|
||||
private _bookTrustManager: IBookTrustManager;
|
||||
public bookPinManager: IBookPinManager;
|
||||
|
||||
private _bookViewer: vscode.TreeView<BookTreeItem>;
|
||||
public viewId: string;
|
||||
@@ -44,8 +46,9 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
this._openAsUntitled = openAsUntitled;
|
||||
this._extensionContext = extensionContext;
|
||||
this.books = [];
|
||||
this.initialize(workspaceFolders).catch(e => console.error(e));
|
||||
this.bookPinManager = new BookPinManager();
|
||||
this.viewId = view;
|
||||
this.initialize(workspaceFolders).catch(e => console.error(e));
|
||||
this.prompter = new CodeAdapter();
|
||||
this._bookTrustManager = new BookTrustManager(this.books);
|
||||
|
||||
@@ -53,13 +56,24 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
}
|
||||
|
||||
private async initialize(workspaceFolders: vscode.WorkspaceFolder[]): Promise<void> {
|
||||
await Promise.all(workspaceFolders.map(async (workspaceFolder) => {
|
||||
try {
|
||||
await this.loadNotebooksInFolder(workspaceFolder.uri.fsPath);
|
||||
} catch {
|
||||
// no-op, not all workspace folders are going to be valid books
|
||||
}
|
||||
}));
|
||||
if (this.viewId === constants.PINNED_BOOKS_VIEWID) {
|
||||
await Promise.all(getPinnedNotebooks().map(async (notebookPath) => {
|
||||
try {
|
||||
await this.createAndAddBookModel(notebookPath, true);
|
||||
} catch {
|
||||
// no-op, not all workspace folders are going to be valid books
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
await Promise.all(workspaceFolders.map(async (workspaceFolder) => {
|
||||
try {
|
||||
await this.loadNotebooksInFolder(workspaceFolder.uri.fsPath);
|
||||
} catch {
|
||||
// no-op, not all workspace folders are going to be valid books
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._initializeDeferred.resolve();
|
||||
}
|
||||
|
||||
@@ -97,6 +111,26 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
}
|
||||
}
|
||||
|
||||
async pinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
||||
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
||||
if (bookPathToUpdate) {
|
||||
let pinStatusChanged = this.bookPinManager.pinNotebook(bookTreeItem);
|
||||
if (pinStatusChanged) {
|
||||
bookTreeItem.contextValue = 'pinnedNotebook';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async unpinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
||||
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
||||
if (bookPathToUpdate) {
|
||||
let pinStatusChanged = this.bookPinManager.unpinNotebook(bookTreeItem);
|
||||
if (pinStatusChanged) {
|
||||
bookTreeItem.contextValue = 'savedNotebook';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async openBook(bookPath: string, urlToOpen?: string, showPreview?: boolean, isNotebook?: boolean): Promise<void> {
|
||||
try {
|
||||
// Convert path to posix style for easier comparisons
|
||||
@@ -132,6 +166,20 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
}
|
||||
}
|
||||
|
||||
async addNotebookToPinnedView(bookItem: BookTreeItem): Promise<void> {
|
||||
let notebookPath: string = bookItem.book.contentPath;
|
||||
if (notebookPath) {
|
||||
await this.createAndAddBookModel(notebookPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
async removeNotebookFromPinnedView(bookItem: BookTreeItem): Promise<void> {
|
||||
let notebookPath: string = bookItem.book.contentPath;
|
||||
if (notebookPath) {
|
||||
await this.closeBook(bookItem);
|
||||
}
|
||||
}
|
||||
|
||||
@debounce(1500)
|
||||
async fireBookRefresh(book: BookModel): Promise<void> {
|
||||
await book.initializeContents().then(() => {
|
||||
@@ -169,21 +217,23 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
* @param bookPath The path to the book folder to create the model for
|
||||
*/
|
||||
private async createAndAddBookModel(bookPath: string, isNotebook: boolean): Promise<void> {
|
||||
const book: BookModel = new BookModel(bookPath, this._openAsUntitled, isNotebook, this._extensionContext);
|
||||
await book.initializeContents();
|
||||
this.books.push(book);
|
||||
if (!this.currentBook) {
|
||||
this.currentBook = book;
|
||||
}
|
||||
this._bookViewer = vscode.window.createTreeView(this.viewId, { showCollapseAll: true, treeDataProvider: this });
|
||||
this._bookViewer.onDidChangeVisibility(e => {
|
||||
let openDocument = azdata.nb.activeNotebookEditor;
|
||||
let notebookPath = openDocument?.document.uri;
|
||||
// call reveal only once on the correct view
|
||||
if (e.visible && ((!this._openAsUntitled && notebookPath?.scheme !== 'untitled') || (this._openAsUntitled && notebookPath?.scheme === 'untitled'))) {
|
||||
this.revealActiveDocumentInViewlet();
|
||||
if (!this.books.find(x => x.bookPath === bookPath)) {
|
||||
const book: BookModel = new BookModel(bookPath, this._openAsUntitled, isNotebook, this._extensionContext);
|
||||
await book.initializeContents();
|
||||
this.books.push(book);
|
||||
if (!this.currentBook) {
|
||||
this.currentBook = book;
|
||||
}
|
||||
});
|
||||
this._bookViewer = vscode.window.createTreeView(this.viewId, { showCollapseAll: true, treeDataProvider: this });
|
||||
this._bookViewer.onDidChangeVisibility(e => {
|
||||
let openDocument = azdata.nb.activeNotebookEditor;
|
||||
let notebookPath = openDocument?.document.uri;
|
||||
// call reveal only once on the correct view
|
||||
if (e.visible && ((!this._openAsUntitled && notebookPath?.scheme !== 'untitled') || (this._openAsUntitled && notebookPath?.scheme === 'untitled'))) {
|
||||
this.revealActiveDocumentInViewlet();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async showPreviewFile(urlToOpen?: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user