mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 09:35:37 -05:00
* Merge from vscode a348d103d1256a06a2c9b3f9b406298a9fef6898 * Fixes and cleanup * Distro * Fix hygiene yarn * delete no yarn lock changes file * Fix hygiene * Fix layer check * Fix CI * Skip lib checks * Remove tests deleted in vs code * Fix tests * Distro * Fix tests and add removed extension point * Skip failing notebook tests for now * Disable broken tests and cleanup build folder * Update yarn.lock and fix smoke tests * Bump sqlite * fix contributed actions and file spacing * Fix user data path * Update yarn.locks Co-authored-by: ADS Merger <karlb@microsoft.com>
247 lines
10 KiB
TypeScript
247 lines
10 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import 'vs/css!./media/fileBrowserDialog';
|
|
import { Button } from 'sql/base/browser/ui/button/button';
|
|
import { InputBox, OnLoseFocusParams } from 'sql/base/browser/ui/inputBox/inputBox';
|
|
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
|
import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper';
|
|
import { HideReason, Modal } from 'sql/workbench/browser/modal/modal';
|
|
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
|
import { FileNode } from 'sql/workbench/services/fileBrowser/common/fileNode';
|
|
import { FileBrowserTreeView } from 'sql/workbench/services/fileBrowser/browser/fileBrowserTreeView';
|
|
import { FileBrowserViewModel } from 'sql/workbench/services/fileBrowser/common/fileBrowserViewModel';
|
|
|
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
|
import { Event, Emitter } from 'vs/base/common/event';
|
|
import { localize } from 'vs/nls';
|
|
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
|
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
|
import * as DOM from 'vs/base/browser/dom';
|
|
import * as strings from 'vs/base/common/strings';
|
|
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
|
import { ILogService } from 'vs/platform/log/common/log';
|
|
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
|
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
|
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
|
|
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
|
|
|
export class FileBrowserDialog extends Modal {
|
|
private _viewModel: FileBrowserViewModel;
|
|
private _body: HTMLElement;
|
|
private _filePathInputBox: InputBox;
|
|
private _fileFilterSelectBox: SelectBox;
|
|
private _okButton: Button;
|
|
private _cancelButton: Button;
|
|
private _onOk = new Emitter<string>();
|
|
public onOk: Event<string> = this._onOk.event;
|
|
|
|
private _treeContainer: HTMLElement;
|
|
private _fileBrowserTreeView: FileBrowserTreeView;
|
|
private _selectedFilePath: string;
|
|
private _isFolderSelected: boolean;
|
|
|
|
constructor(title: string,
|
|
@ILayoutService layoutService: ILayoutService,
|
|
@IThemeService themeService: IThemeService,
|
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
|
@IContextViewService private _contextViewService: IContextViewService,
|
|
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
|
@IContextKeyService contextKeyService: IContextKeyService,
|
|
@IClipboardService clipboardService: IClipboardService,
|
|
@ILogService logService: ILogService,
|
|
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
|
) {
|
|
super(title, TelemetryKeys.ModalDialogName.FileBrowser, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { dialogStyle: 'flyout', hasTitleIcon: false, hasBackButton: true, hasSpinner: true });
|
|
this._viewModel = this._instantiationService.createInstance(FileBrowserViewModel);
|
|
this._viewModel.onAddFileTree(args => this.handleOnAddFileTree(args.rootNode, args.selectedNode, args.expandedNodes).catch(err => onUnexpectedError(err)));
|
|
this._viewModel.onPathValidate(args => this.handleOnValidate(args.succeeded, args.message));
|
|
}
|
|
|
|
protected layout(height?: number): void {
|
|
}
|
|
|
|
protected renderBody(container: HTMLElement) {
|
|
this._body = DOM.append(container, DOM.$('.file-browser-dialog'));
|
|
}
|
|
|
|
public override render() {
|
|
super.render();
|
|
attachModalDialogStyler(this, this._themeService);
|
|
|
|
if (this.backButton) {
|
|
|
|
this.backButton.onDidClick(() => {
|
|
this.close();
|
|
});
|
|
|
|
this._register(attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }));
|
|
}
|
|
|
|
this._treeContainer = DOM.append(this._body, DOM.$('.tree-view'));
|
|
|
|
let tableContainer: HTMLElement = DOM.append(DOM.append(this._body, DOM.$('.option-section')), DOM.$('table.file-table-content'));
|
|
tableContainer.setAttribute('role', 'presentation');
|
|
|
|
let pathLabel = localize('filebrowser.filepath', "Selected path");
|
|
let pathBuilder = DialogHelper.appendRow(tableContainer, pathLabel, 'file-input-label', 'file-input-box');
|
|
this._filePathInputBox = new InputBox(pathBuilder, this._contextViewService, {
|
|
ariaLabel: pathLabel
|
|
});
|
|
|
|
let filterLabel = localize('fileFilter', "Files of type");
|
|
this._fileFilterSelectBox = new SelectBox(['*'], '*', this._contextViewService);
|
|
this._fileFilterSelectBox.setAriaLabel(filterLabel);
|
|
let filterBuilder = DialogHelper.appendRow(tableContainer, filterLabel, 'file-input-label', 'file-input-box');
|
|
DialogHelper.appendInputSelectBox(filterBuilder, this._fileFilterSelectBox);
|
|
|
|
this._okButton = this.addFooterButton(localize('fileBrowser.ok', "OK"), () => this.ok());
|
|
this._okButton.enabled = false;
|
|
this._cancelButton = this.addFooterButton(localize('fileBrowser.discard', "Discard"), () => this.close(), 'right', true);
|
|
|
|
this.registerListeners();
|
|
this.updateTheme();
|
|
}
|
|
|
|
public open(ownerUri: string,
|
|
expandPath: string,
|
|
fileFilters: [{ label: string, filters: string[] }],
|
|
fileValidationServiceType: string,
|
|
): void {
|
|
this._viewModel.initialize(ownerUri, expandPath, fileFilters, fileValidationServiceType);
|
|
this._fileFilterSelectBox.setOptions(this._viewModel.formattedFileFilters);
|
|
this._fileFilterSelectBox.select(0);
|
|
this._filePathInputBox.value = expandPath;
|
|
this._isFolderSelected = true;
|
|
this.enableOkButton();
|
|
this.spinner = true;
|
|
this.show();
|
|
|
|
this._fileBrowserTreeView = this._instantiationService.createInstance(FileBrowserTreeView);
|
|
this._fileBrowserTreeView.setOnClickedCallback((arg) => this.onClicked(arg));
|
|
this._fileBrowserTreeView.setOnDoubleClickedCallback((arg) => this.onDoubleClicked(arg));
|
|
this._viewModel.openFileBrowser(0, false).catch(err => onUnexpectedError(err));
|
|
}
|
|
|
|
/* enter key */
|
|
protected override onAccept() {
|
|
if (this._okButton.enabled === true) {
|
|
this.ok();
|
|
}
|
|
}
|
|
|
|
private async handleOnAddFileTree(rootNode: FileNode, selectedNode: FileNode, expandedNodes: FileNode[]): Promise<void> {
|
|
await this.updateFileTree(rootNode, selectedNode, expandedNodes);
|
|
this.spinner = false;
|
|
}
|
|
|
|
private enableOkButton() {
|
|
if (strings.isFalsyOrWhitespace(this._selectedFilePath) || this._isFolderSelected === true) {
|
|
this._okButton.enabled = false;
|
|
} else {
|
|
this._okButton.enabled = true;
|
|
}
|
|
}
|
|
|
|
private onClicked(selectedNode: FileNode) {
|
|
this._filePathInputBox.value = selectedNode.fullPath;
|
|
|
|
if (selectedNode.isFile === true) {
|
|
this._isFolderSelected = false;
|
|
} else {
|
|
this._isFolderSelected = true;
|
|
}
|
|
|
|
this.enableOkButton();
|
|
}
|
|
|
|
private onDoubleClicked(selectedNode: FileNode) {
|
|
if (selectedNode.isFile === true) {
|
|
this.ok();
|
|
}
|
|
}
|
|
|
|
private onFilePathChange(filePath: string) {
|
|
this._isFolderSelected = false;
|
|
this._selectedFilePath = filePath;
|
|
|
|
this._filePathInputBox.hideMessage();
|
|
this.enableOkButton();
|
|
}
|
|
|
|
private async onFilePathBlur(params: OnLoseFocusParams): Promise<boolean> {
|
|
if (!strings.isFalsyOrWhitespace(params.value)) {
|
|
return this._viewModel.validateFilePaths([params.value]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private ok() {
|
|
this._onOk.fire(this._selectedFilePath);
|
|
this.close('ok');
|
|
}
|
|
|
|
private handleOnValidate(succeeded: boolean, errorMessage: string) {
|
|
if (succeeded === false) {
|
|
if (strings.isFalsyOrWhitespace(errorMessage)) {
|
|
errorMessage = 'The provided path is invalid.';
|
|
}
|
|
this._filePathInputBox.showMessage({ type: MessageType.ERROR, content: errorMessage });
|
|
}
|
|
}
|
|
|
|
private close(hideReason: HideReason = 'close'): void {
|
|
if (this._fileBrowserTreeView) {
|
|
this._fileBrowserTreeView.dispose();
|
|
}
|
|
this._onOk.dispose();
|
|
this.hide(hideReason);
|
|
this._viewModel.closeFileBrowser().catch(err => onUnexpectedError(err));
|
|
}
|
|
|
|
private async updateFileTree(rootNode: FileNode, selectedNode: FileNode, expandedNodes: FileNode[]): Promise<void> {
|
|
await this._fileBrowserTreeView.renderBody(this._treeContainer, rootNode, selectedNode, expandedNodes);
|
|
this._fileBrowserTreeView.setVisible(true);
|
|
this._fileBrowserTreeView.layout(DOM.getTotalHeight(this._treeContainer));
|
|
}
|
|
|
|
private async onFilterSelectChanged(filterIndex): Promise<void> {
|
|
this.spinner = true;
|
|
await this._viewModel.openFileBrowser(filterIndex, true);
|
|
}
|
|
|
|
private registerListeners(): void {
|
|
this._register(this._fileFilterSelectBox.onDidSelect(selectData => {
|
|
this.onFilterSelectChanged(selectData.index).catch(err => onUnexpectedError(err));
|
|
}));
|
|
this._register(this._filePathInputBox.onDidChange(e => {
|
|
this.onFilePathChange(e);
|
|
}));
|
|
this._register(this._filePathInputBox.onLoseFocus((params: OnLoseFocusParams) => {
|
|
this.onFilePathBlur(params).catch(err => onUnexpectedError(err));
|
|
}));
|
|
|
|
// Theme styler
|
|
this._register(attachInputBoxStyler(this._filePathInputBox, this._themeService));
|
|
this._register(attachSelectBoxStyler(this._fileFilterSelectBox, this._themeService));
|
|
this._register(attachButtonStyler(this._okButton, this._themeService));
|
|
this._register(attachButtonStyler(this._cancelButton, this._themeService));
|
|
|
|
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme()));
|
|
}
|
|
|
|
// Update theming that is specific to file browser
|
|
private updateTheme(): void {
|
|
if (this._treeContainer) {
|
|
this._treeContainer.style.backgroundColor = this.headerAndFooterBackground;
|
|
}
|
|
}
|
|
}
|