From 7e98727d4cc6f14e83617ba1c79edf486d4d72f5 Mon Sep 17 00:00:00 2001 From: Maddy <12754347+MaddyDev@users.noreply.github.com> Date: Mon, 22 Jul 2019 17:00:02 -0700 Subject: [PATCH] Dashboard/book widget (#6447) * initial commit: new modelview widget that shows books * moved the new command action to notebook contribution and addressed the comments * localize changes * removed changes from src/vs file and string changes * make directory for each contribution * typo fix --- extensions/mssql/package.json | 15 +- extensions/mssql/package.nls.json | 1 + extensions/mssql/src/dashboard/bookWidget.ts | 128 ++++++++++++++++++ extensions/mssql/src/main.ts | 4 + .../hdfsCommands.ts | 2 +- .../electron-browser/notebook.contribution.ts | 30 +++- 6 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 extensions/mssql/src/dashboard/bookWidget.ts diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index dfc5612f03..3251b4d3ac 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -417,7 +417,7 @@ { "name": "%title.endpoints%", "row": 0, - "col": 2, + "col": 4, "rowspan": 1.5, "colspan": 2, "widget": { @@ -425,6 +425,17 @@ "id": "bdc-endpoints" } } + }, + { + "name": "%title.books%", + "row": 0, + "col": 2, + "colspan": 1, + "widget": { + "modelview": { + "id": "books-widget" + } + } } ] } @@ -932,4 +943,4 @@ ] } } -} +} \ No newline at end of file diff --git a/extensions/mssql/package.nls.json b/extensions/mssql/package.nls.json index 4eeae5d413..0646483ab3 100644 --- a/extensions/mssql/package.nls.json +++ b/extensions/mssql/package.nls.json @@ -31,6 +31,7 @@ "title.clearSearchServerResult": "Search: Clear Search Server Results", "title.endpoints": "Service Endpoints", + "title.books": "Notebooks", "mssql.configuration.title": "MSSQL configuration", "mssql.query.displayBitAsNumber": "Should BIT columns be displayed as numbers (1 or 0)? If false, BIT columns will be displayed as 'true' or 'false'", diff --git a/extensions/mssql/src/dashboard/bookWidget.ts b/extensions/mssql/src/dashboard/bookWidget.ts new file mode 100644 index 0000000000..835714bafb --- /dev/null +++ b/extensions/mssql/src/dashboard/bookWidget.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as azdata from 'azdata'; +import * as nls from 'vscode-nls'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { + BookContributionProvider, BookContribution +} from './bookExtensions'; +import * as Utils from '../utils'; + +const localize = nls.loadMessageBundle(); + +export function registerBooksWidget(bookContributionProvider: BookContributionProvider): void { + azdata.ui.registerModelViewProvider('books-widget', async (view) => { + const container = view.modelBuilder.flexContainer().withLayout({ + flexFlow: 'column', + width: '100%', + height: '100%', + alignItems: 'left' + }).component(); + const bookslocationContainer = view.modelBuilder.flexContainer().withLayout({ + flexFlow: 'column', + width: '270px', + height: '100%', + alignItems: 'left', + position: 'absolute' + }).component(); + + let books = bookContributionProvider.contributions.map(contribution => { + const bookRow = view.modelBuilder.flexContainer().withLayout({ + flexFlow: 'row' + }).component(); + const tsgbooklink = view.modelBuilder.button().withProperties({ + label: contribution.name, + title: contribution.name + }).component(); + tsgbooklink.onDidClick(() => { + promptForFolder(contribution); + }); + bookRow.addItem(tsgbooklink, { + CSSStyles: { + 'width': '100%', + 'color': '#0078d4', + 'text-decoration': 'underline', + 'padding-top': '10px', + 'text-align': 'left' + } + }); + return bookRow; + }); + + + container.addItems(books, { + CSSStyles: { + 'padding-left': '10px', + 'border-top': 'solid 1px #ccc', + 'box-sizing': 'border-box', + 'user-select': 'text' + } + }); + bookslocationContainer.addItem(container, { + CSSStyles: { + 'padding-top': '25px', + 'padding-left': '5px' + } + }); + await view.initializeModel(bookslocationContainer); + }); +} + +async function promptForFolder(bookContribution: BookContribution): Promise { + try { + const allFilesFilter = localize('allFiles', "All Files"); + let filter = {}; + filter[allFilesFilter] = '*'; + let uris = await vscode.window.showOpenDialog({ + filters: filter, + canSelectFiles: false, + canSelectMany: false, + canSelectFolders: true, + openLabel: localize('labelPickFolder', "Pick Folder") + }); + if (uris && uris.length > 0) { + let pickedFolder = uris[0]; + await saveBooksToFolder(pickedFolder, bookContribution); + await promptToReloadWindow(pickedFolder); + } + return; + } catch (error) { + vscode.window.showErrorMessage(localize('FailedDuringSaveAndPrompt', 'Failed : {0}', Utils.getErrorMessage(error))); + } +} + +async function saveBooksToFolder(folderUri: vscode.Uri, bookContribution: BookContribution): Promise { + // Get book contributions + if (bookContribution && folderUri) { + //remove folder if exists + await fs.removeSync(path.join(folderUri.path, bookContribution.name)); + //copy them from the books extension: + const destinationFolder = path.join(folderUri.path, bookContribution.name); + fs.mkdirSync(destinationFolder); + await fs.copy(bookContribution.path, destinationFolder); + } +} +function promptToReloadWindow(folderUri: vscode.Uri): void { + const actionReload = localize('prompt.reloadInstance', "Reload"); + const actionOpenNew = localize('prompt.openNewInstance', "Open new instance"); + vscode.window.showInformationMessage(localize('prompt.reloadDescription', "Reload and view the Jupyter Books in the Files view."), actionReload, actionOpenNew) + .then(selectedAction => { + if (selectedAction === actionReload) { + vscode.commands.executeCommand('workbench.action.setWorkspaceAndOpen', { + forceNewWindow: false, + folderPath: folderUri + }); + } + if (selectedAction === actionOpenNew) { + vscode.commands.executeCommand('workbench.action.setWorkspaceAndOpen', { + forceNewWindow: true, + folderPath: folderUri + }); + } + }); +} \ No newline at end of file diff --git a/extensions/mssql/src/main.ts b/extensions/mssql/src/main.ts index 6761fb3545..1b8de6e186 100644 --- a/extensions/mssql/src/main.ts +++ b/extensions/mssql/src/main.ts @@ -8,6 +8,7 @@ import * as azdata from 'azdata'; import * as path from 'path'; import * as os from 'os'; import * as nls from 'vscode-nls'; + const localize = nls.loadMessageBundle(); import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client'; @@ -35,6 +36,7 @@ import { registerSearchServerCommand } from './objectExplorerNodeProvider/comman import { MssqlIconProvider } from './iconProvider'; import { registerServiceEndpoints } from './dashboard/serviceEndpoints'; import { getBookExtensionContributions } from './dashboard/bookExtensions'; +import { registerBooksWidget } from './dashboard/bookWidget'; const baseConfig = require('./config.json'); const outputChannel = vscode.window.createOutputChannel(Constants.serviceName); @@ -121,6 +123,8 @@ export async function activate(context: vscode.ExtensionContext): Promise { try { let folderNode = await getNode(context, this.appContext); - const allFilesFilter = localize('allFiles', 'All Files'); + const allFilesFilter = localize('allFiles', "All Files"); let filter = {}; filter[allFilesFilter] = '*'; if (folderNode) { diff --git a/src/sql/workbench/parts/notebook/electron-browser/notebook.contribution.ts b/src/sql/workbench/parts/notebook/electron-browser/notebook.contribution.ts index a6b4824b5f..7e9c81b4ea 100644 --- a/src/sql/workbench/parts/notebook/electron-browser/notebook.contribution.ts +++ b/src/sql/workbench/parts/notebook/electron-browser/notebook.contribution.ts @@ -6,7 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; import { NotebookInput } from 'sql/workbench/parts/notebook/node/notebookInput'; import { NotebookEditor } from 'sql/workbench/parts/notebook/electron-browser/notebookEditor'; @@ -21,6 +21,11 @@ import { PlotlyOutputComponent } from 'sql/workbench/parts/notebook/outputs/plot import { registerComponentType } from 'sql/workbench/parts/notebook/electron-browser/outputs/mimeRegistry'; import { MimeRendererComponent } from 'sql/workbench/parts/notebook/electron-browser/outputs/mimeRenderer.component'; import { MarkdownOutputComponent } from 'sql/workbench/parts/notebook/electron-browser/outputs/markdownOutput.component'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { Uri } from 'vscode'; // Model View editor registration const viewModelEditorDescriptor = new EditorDescriptor( @@ -45,6 +50,29 @@ actionRegistry.registerWorkbenchAction( ), NewNotebookAction.LABEL ); + +registerAction({ + id: 'workbench.action.setWorkspaceAndOpen', + handler: async (accessor, options: { forceNewWindow: boolean, folderPath: Uri }) => { + const viewletService = accessor.get(IViewletService); + const workspaceEditingService = accessor.get(IWorkspaceEditingService); + const windowService = accessor.get(IWindowService); + let folders = []; + if (!options.folderPath) { + return; + } + folders.push(options.folderPath); + await workspaceEditingService.addFolders(folders.map(folder => ({ uri: folder }))); + await viewletService.openViewlet(viewletService.getDefaultViewletId(), true); + if (options.forceNewWindow) { + return windowService.openWindow([{ folderUri: folders[0] }], { forceNewWindow: options.forceNewWindow }); + } + else { + return windowService.reloadWindow(); + } + } +}); + const configurationRegistry = Registry.as(ConfigExtensions.Configuration); configurationRegistry.registerConfiguration({ 'id': 'notebook',