mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Editing Books (#13535)
* start work on ui * Move notebooks complete * Simplify version handling * fix issues with add section method * fix issues with the edit experience and add the quick pick for editing * add version handler for re-writing tocs * fix book toc manager tests * add notebook test * fix localize constant * normalize paths on windows * check that a section has sections before attempting to mve files * Implement method for renaming duplicated files * moving last notebook from section converts the section into notebook * Add recovery method restore original state of file system * Add clean up, for files that are copied instead of renamed * remove dir complexity * divide edit book in methods for easier testing and remove promise chain * Keep structure of toc * normalize paths on windows * fix external link * Add other fields for versions * fix paths in uri of findSection * add section to section test * Add error messages * address pr comments and add tests * check that the new path of a notebook is different from the original path before deleting
This commit is contained in:
@@ -11,19 +11,17 @@ import * as constants from '../common/constants';
|
||||
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
|
||||
import CodeAdapter from '../prompts/adapter';
|
||||
import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
|
||||
import { BookModel, BookVersion } from './bookModel';
|
||||
import { BookModel } from './bookModel';
|
||||
import { Deferred } from '../common/promise';
|
||||
import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
|
||||
import * as loc from '../common/localizedConstants';
|
||||
import * as glob from 'fast-glob';
|
||||
import { IJupyterBookSectionV2, IJupyterBookSectionV1 } from '../contracts/content';
|
||||
import { debounce, getPinnedNotebooks } from '../common/utils';
|
||||
import { IBookPinManager, BookPinManager } from './bookPinManager';
|
||||
import { BookTocManager, IBookTocManager } from './bookTocManager';
|
||||
import { BookTocManager, IBookTocManager, quickPickResults } from './bookTocManager';
|
||||
import { getContentPath } from './bookVersionHandler';
|
||||
import { TelemetryReporter, BookTelemetryView, NbTelemetryActions } from '../telemetry';
|
||||
|
||||
const content = 'content';
|
||||
|
||||
interface BookSearchResults {
|
||||
notebookPaths: string[];
|
||||
bookPaths: string[];
|
||||
@@ -50,7 +48,6 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
this._extensionContext = extensionContext;
|
||||
this.books = [];
|
||||
this.bookPinManager = new BookPinManager();
|
||||
this.bookTocManager = new BookTocManager();
|
||||
this.viewId = view;
|
||||
this.initialize(workspaceFolders).catch(e => console.error(e));
|
||||
this.prompter = new CodeAdapter();
|
||||
@@ -93,6 +90,14 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
this._extensionContext.globalState.update(constants.visitedNotebooksMementoKey, value);
|
||||
}
|
||||
|
||||
setFileWatcher(book: BookModel): void {
|
||||
fs.watchFile(book.tableOfContentsPath, async (curr, prev) => {
|
||||
if (curr.mtime > prev.mtime) {
|
||||
this.fireBookRefresh(book);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
trustBook(bookTreeItem?: BookTreeItem): void {
|
||||
let bookPathToTrust: string = bookTreeItem ? bookTreeItem.root : this.currentBook?.bookPath;
|
||||
if (bookPathToTrust) {
|
||||
@@ -144,8 +149,92 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.CreateBook).send();
|
||||
}
|
||||
|
||||
async editBook(book: BookTreeItem, section: BookTreeItem): Promise<void> {
|
||||
await this.bookTocManager.updateBook(section, book);
|
||||
async getSelectionQuickPick(movingElement: BookTreeItem): Promise<quickPickResults> {
|
||||
let bookOptions: vscode.QuickPickItem[] = [];
|
||||
let pickedSection: vscode.QuickPickItem;
|
||||
this.books.forEach(book => {
|
||||
if (!book.isNotebook) {
|
||||
bookOptions.push({ label: book.bookItems[0].title, detail: book.bookPath });
|
||||
}
|
||||
});
|
||||
let pickedBook = await vscode.window.showQuickPick(bookOptions, {
|
||||
canPickMany: false,
|
||||
placeHolder: loc.labelBookFolder
|
||||
});
|
||||
|
||||
if (pickedBook && movingElement) {
|
||||
const updateBook = this.books.find(book => book.bookPath === pickedBook.detail).bookItems[0];
|
||||
if (updateBook) {
|
||||
let bookSections = updateBook.sections;
|
||||
while (bookSections?.length > 0) {
|
||||
bookOptions = [{ label: loc.labelAddToLevel, detail: pickedSection ? pickedSection.detail : '' }];
|
||||
bookSections.forEach(section => {
|
||||
if (section.sections) {
|
||||
bookOptions.push({ label: section.title ? section.title : section.file, detail: section.file });
|
||||
}
|
||||
});
|
||||
bookSections = [];
|
||||
if (bookOptions.length > 1) {
|
||||
pickedSection = await vscode.window.showQuickPick(bookOptions, {
|
||||
canPickMany: false,
|
||||
placeHolder: loc.labelBookSection
|
||||
});
|
||||
|
||||
if (pickedSection && pickedSection.label === loc.labelAddToLevel) {
|
||||
break;
|
||||
}
|
||||
else if (pickedSection && pickedSection.detail) {
|
||||
if (updateBook.root === movingElement.root && pickedSection.detail === movingElement.uri) {
|
||||
pickedSection = undefined;
|
||||
} else {
|
||||
bookSections = updateBook.findChildSection(pickedSection.detail).sections;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { quickPickSection: pickedSection, book: updateBook };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async editBook(movingElement: BookTreeItem): Promise<void> {
|
||||
const selectionResults = await this.getSelectionQuickPick(movingElement);
|
||||
const pickedSection = selectionResults.quickPickSection;
|
||||
const updateBook = selectionResults.book;
|
||||
if (pickedSection && updateBook) {
|
||||
const targetSection = pickedSection.detail !== undefined ? updateBook.findChildSection(pickedSection.detail) : undefined;
|
||||
if (movingElement.tableOfContents.sections) {
|
||||
if (movingElement.contextValue === 'savedNotebook') {
|
||||
let sourceBook = this.books.find(book => book.getNotebook(path.normalize(movingElement.book.contentPath)));
|
||||
movingElement.tableOfContents.sections = sourceBook?.bookItems[0].sections;
|
||||
}
|
||||
}
|
||||
const sourceBook = this.books.find(book => book.bookPath === movingElement.book.root);
|
||||
const targetBook = this.books.find(book => book.bookPath === updateBook.book.root);
|
||||
this.bookTocManager = new BookTocManager(targetBook, sourceBook);
|
||||
// remove watch on toc file from both books.
|
||||
if (sourceBook) {
|
||||
fs.unwatchFile(movingElement.tableOfContentsPath);
|
||||
}
|
||||
try {
|
||||
await this.bookTocManager.updateBook(movingElement, updateBook, targetSection);
|
||||
} catch (e) {
|
||||
await this.bookTocManager.recovery();
|
||||
vscode.window.showErrorMessage(loc.editBookError(updateBook.book.contentPath, e instanceof Error ? e.message : e));
|
||||
} finally {
|
||||
this.fireBookRefresh(targetBook);
|
||||
if (sourceBook) {
|
||||
// refresh source book model to pick up latest changes
|
||||
this.fireBookRefresh(sourceBook);
|
||||
}
|
||||
// even if it fails, we still need to watch the toc file again.
|
||||
if (sourceBook) {
|
||||
this.setFileWatcher(sourceBook);
|
||||
}
|
||||
this.setFileWatcher(targetBook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async openBook(bookPath: string, urlToOpen?: string, showPreview?: boolean, isNotebook?: boolean): Promise<void> {
|
||||
@@ -180,6 +269,11 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// if there is an error remove book from context
|
||||
const index = this.books.findIndex(book => book.bookPath === bookPath);
|
||||
if (index !== -1) {
|
||||
this.books.splice(index, 1);
|
||||
}
|
||||
vscode.window.showErrorMessage(loc.openFileError(bookPath, e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
@@ -267,9 +361,9 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
if (urlToOpen) {
|
||||
const bookRoot = this.currentBook.bookItems[0];
|
||||
const sectionToOpen = bookRoot.findChildSection(urlToOpen);
|
||||
urlPath = sectionToOpen?.url;
|
||||
urlPath = sectionToOpen?.file;
|
||||
} else {
|
||||
urlPath = this.currentBook.version === BookVersion.v1 ? (this.currentBook.bookItems[0].tableOfContents.sections[0] as IJupyterBookSectionV1).url : (this.currentBook.bookItems[0].tableOfContents.sections[0] as IJupyterBookSectionV2).file;
|
||||
urlPath = this.currentBook.bookItems[0].tableOfContents.sections[0].file;
|
||||
}
|
||||
}
|
||||
if (urlPath) {
|
||||
@@ -434,20 +528,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
public async searchJupyterBooks(treeItem?: BookTreeItem): Promise<void> {
|
||||
let folderToSearch: string;
|
||||
if (treeItem && treeItem.sections !== undefined) {
|
||||
if (treeItem.book.version === BookVersion.v1) {
|
||||
if (treeItem.uri) {
|
||||
folderToSearch = path.join(treeItem.book.root, content, path.dirname(treeItem.uri));
|
||||
} else {
|
||||
folderToSearch = path.join(treeItem.root, content);
|
||||
}
|
||||
} else if (treeItem.book.version === BookVersion.v2) {
|
||||
if (treeItem.uri) {
|
||||
folderToSearch = path.join(treeItem.book.root, path.dirname(treeItem.uri));
|
||||
} else {
|
||||
folderToSearch = path.join(treeItem.root);
|
||||
}
|
||||
}
|
||||
|
||||
folderToSearch = treeItem.uri ? getContentPath(treeItem.version, treeItem.book.root, path.dirname(treeItem.uri)) : getContentPath(treeItem.version, treeItem.book.root, '');
|
||||
} else if (this.currentBook && !this.currentBook.isNotebook) {
|
||||
folderToSearch = path.join(this.currentBook.contentFolderPath);
|
||||
} else {
|
||||
@@ -573,19 +654,14 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional method on the vscode interface.
|
||||
* Implementing getParent, due to reveal method in extHostTreeView.ts
|
||||
* throwing error if it is not implemented.
|
||||
*/
|
||||
getParent(element?: BookTreeItem): vscode.ProviderResult<BookTreeItem> {
|
||||
if (element?.uri) {
|
||||
let parentPath: string;
|
||||
let contentFolder = element.book.version === BookVersion.v1 ? path.join(element.book.root, content) : element.book.root;
|
||||
parentPath = path.join(contentFolder, element.uri.substring(0, element.uri.lastIndexOf(path.posix.sep)));
|
||||
if (parentPath === element.root) {
|
||||
return undefined;
|
||||
}
|
||||
let parentPaths = Array.from(this.currentBook.getAllNotebooks()?.keys()).filter(x => x.indexOf(parentPath) > -1);
|
||||
return parentPaths.length > 0 ? this.currentBook.getAllNotebooks().get(parentPaths[0]) : undefined;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
// Remove it for perf issues.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getUntitledNotebookUri(resource: string): vscode.Uri {
|
||||
|
||||
Reference in New Issue
Block a user