Migrate Notebook Upload File to React (#581)

Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
This commit is contained in:
Hardikkumar Nai
2021-04-01 05:55:45 +05:30
committed by GitHub
parent c68e84a4b9
commit 5307f6bb5b
18 changed files with 26590 additions and 573 deletions

View File

@@ -15,7 +15,6 @@ import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html";
import TableColumnOptionsPaneTemplate from "./Tables/TableColumnOptionsPane.html";
import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html";
import TableQuerySelectPaneTemplate from "./Tables/TableQuerySelectPane.html";
import UploadFilePaneTemplate from "./UploadFilePane.html";
export class PaneComponent {
constructor(data: any) {
@@ -149,15 +148,6 @@ export class BrowseQueriesPaneComponent {
}
}
export class UploadFilePaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: UploadFilePaneTemplate,
};
}
}
export class StringInputPaneComponent {
constructor() {
return {

View File

@@ -410,23 +410,6 @@ exports[`Settings Pane should render Default properly 1`] = `
"title": [Function],
"visible": [Function],
},
UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
@@ -1016,23 +999,6 @@ exports[`Settings Pane should render Default properly 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
}
}
formError=""
@@ -1548,23 +1514,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
"title": [Function],
"visible": [Function],
},
UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
@@ -2154,23 +2103,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
}
}
formError=""

View File

@@ -1,83 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="uploadFilePane">
<!-- Upload File form -- Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- Upload File header - Start -->
<div class="firstdivbg headerline">
<span role="heading" aria-level="2" data-bind="text: title"></span>
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: cancel">
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Upload File header - End -->
<!-- Upload File errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
data-bind="visible: formErrors() && formErrors() !== ''"
>
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error" /></span>
<span class="warningErrorDetailsLinkContainer">
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
<a
class="errorLink"
role="link"
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '', click: showErrorDetails"
>More details</a
>
</span>
</div>
</div>
<!-- Upload File errors - End -->
<!-- Upload File inputs - Start -->
<div class="paneMainContent">
<div>
<div class="renewUploadItemsHeader" data-bind="text: selectFileInputLabel"></div>
<input class="importFilesTitle" type="text" disabled data-bind="value: selectedFilesTitle" />
<input
type="file"
id="importFileInput"
style="display: none"
data-bind="event: { change: updateSelectedFiles }, attr: { accept: extensions }"
/>
<a
href="#"
id="fileImportLinkNotebook"
data-bind="event: { click: onImportLinkClick, keypress: onImportLinkKeyPress }"
>
<img
id="importFileButton"
class="fileImportImg"
src="/folder_16x16.svg"
alt="upload files"
title="Upload files"
/>
</a>
</div>
</div>
<div class="paneFooter">
<div class="leftpanel-okbut">
<input
id="uploadFileButton"
type="submit"
data-bind="attr: { value: submitButtonLabel }"
class="btncreatecoll1"
/>
</div>
</div>
<!-- Upload File inputs - End -->
</form>
</div>
<!-- Upload File form - Start -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -1,137 +0,0 @@
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
export interface UploadFilePaneOpenOptions {
paneTitle: string;
selectFileInputLabel: string;
errorMessage: string; // Could not upload notebook
inProgressMessage: string; // Uploading notebook
successMessage: string; // Successfully uploaded notebook
onSubmit: (file: File) => Promise<any>;
extensions?: string; // input accept field. E.g: .ipynb
submitButtonLabel?: string;
}
export class UploadFilePane extends ContextualPaneBase {
public selectedFilesTitle: ko.Observable<string>;
public files: ko.Observable<FileList>;
private openOptions: UploadFilePaneOpenOptions;
private submitButtonLabel: ko.Observable<string>;
private selectFileInputLabel: ko.Observable<string>;
private extensions: ko.Observable<string>;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.resetData();
this.selectFileInputLabel = ko.observable("");
this.selectedFilesTitle = ko.observable<string>("");
this.extensions = ko.observable(null);
this.submitButtonLabel = ko.observable("Load");
this.files = ko.observable<FileList>();
this.files.subscribe((newFiles: FileList) => this.updateSelectedFilesTitle(newFiles));
}
public submit() {
this.formErrors("");
this.formErrorsDetails("");
if (!this.files() || this.files().length === 0) {
this.formErrors("No file specified");
this.formErrorsDetails("No file specified. Please input a file.");
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`${this.openOptions.errorMessage} -- No file specified. Please input a file.`
);
return;
}
const file: File = this.files().item(0);
const id: string = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`${this.openOptions.inProgressMessage}: ${file.name}`
);
this.isExecuting(true);
this.openOptions
.onSubmit(this.files().item(0))
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`${this.openOptions.successMessage} ${file.name}`
);
this.close();
},
(error: any) => {
this.formErrors(this.openOptions.errorMessage);
this.formErrorsDetails(`${this.openOptions.errorMessage}: ${error}`);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`${this.openOptions.errorMessage} ${file.name}: ${error}`
);
}
)
.finally(() => {
this.isExecuting(false);
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
}
public updateSelectedFiles(element: any, event: any): void {
this.files(event.target.files);
}
public close() {
super.close();
this.resetData();
this.files(undefined);
this.resetFileInput();
}
public openWithOptions(options: UploadFilePaneOpenOptions): void {
this.openOptions = options;
this.title(this.openOptions.paneTitle);
if (this.openOptions.submitButtonLabel) {
this.submitButtonLabel(this.openOptions.submitButtonLabel);
}
this.selectFileInputLabel(this.openOptions.selectFileInputLabel);
if (this.openOptions.extensions) {
this.extensions(this.openOptions.extensions);
}
super.open();
}
public onImportLinkClick(source: any, event: MouseEvent): boolean {
document.getElementById("importFileInput").click();
return false;
}
public onImportLinkKeyPress = (source: any, event: KeyboardEvent): boolean => {
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
this.onImportLinkClick(source, null);
return false;
}
return true;
};
private updateSelectedFilesTitle(fileList: FileList) {
this.selectedFilesTitle("");
if (!fileList || fileList.length === 0) {
return;
}
for (let i = 0; i < fileList.length; i++) {
const originalTitle = this.selectedFilesTitle();
this.selectedFilesTitle(originalTitle + `"${fileList.item(i).name}"`);
}
}
private resetFileInput(): void {
const inputElement = $("#importFileInput");
inputElement.wrap("<form>").closest("form").get(0).reset();
inputElement.unwrap();
}
}

View File

@@ -0,0 +1,111 @@
import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent";
export interface UploadFilePanelProps {
explorer: Explorer;
closePanel: () => void;
uploadFile: (name: string, content: string) => Promise<NotebookContentItem>;
}
export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
explorer: container,
closePanel,
uploadFile,
}: UploadFilePanelProps) => {
const title = "Upload file to notebook server";
const submitButtonLabel = "Upload";
const selectFileInputLabel = "Select file to upload";
const extensions: string = undefined; //ex. ".ipynb"
const errorMessage = "Could not upload file";
const inProgressMessage = "Uploading file to notebook server";
const successMessage = "Successfully uploaded file to notebook server";
const [files, setFiles] = useState<FileList>();
const [formErrors, setFormErrors] = useState<string>("");
const [formErrorsDetails, setFormErrorsDetails] = useState<string>("");
const [isExecuting, setIsExecuting] = useState<boolean>(false);
const submit = () => {
setFormErrors("");
setFormErrorsDetails("");
if (!files || files.length === 0) {
setFormErrors("No file specified");
setFormErrorsDetails("No file specified. Please input a file.");
logConsoleError(`${errorMessage} -- No file specified. Please input a file.`);
return;
}
const file: File = files.item(0);
// const id: string = logConsoleProgress(
// `${inProgressMessage}: ${file.name}`
// );
logConsoleProgress(`${inProgressMessage}: ${file.name}`);
setIsExecuting(true);
onSubmit(files.item(0))
.then(
() => {
logConsoleInfo(`${successMessage} ${file.name}`);
closePanel();
},
(error: string) => {
setFormErrors(errorMessage);
setFormErrorsDetails(`${errorMessage}: ${error}`);
logConsoleError(`${errorMessage} ${file.name}: ${error}`);
}
)
.finally(() => {
setIsExecuting(false);
// clearInProgressMessageWithId(id);
});
};
const updateSelectedFiles = (event: ChangeEvent<HTMLInputElement>): void => {
setFiles(event.target.files);
};
const onSubmit = async (file: File): Promise<NotebookContentItem> => {
const readFileAsText = (inputFile: File): Promise<string> => {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onerror = () => {
reader.abort();
reject(`Problem parsing file: ${inputFile}`);
};
reader.onload = () => {
resolve(reader.result as string);
};
reader.readAsText(inputFile);
});
};
const fileContent = await readFileAsText(file);
return uploadFile(file.name, fileContent);
};
const genericPaneProps: GenericRightPaneProps = {
container: container,
formError: formErrors,
formErrorDetail: formErrorsDetails,
id: "uploadFilePane",
isExecuting: isExecuting,
title,
submitButtonText: submitButtonLabel,
onClose: closePanel,
onSubmit: submit,
};
return (
<GenericRightPaneComponent {...genericPaneProps}>
<div className="paneMainContent">
<Upload label={selectFileInputLabel} accept={extensions} onUpload={updateSelectedFiles} />
</div>
</GenericRightPaneComponent>
);
};

View File

@@ -410,23 +410,6 @@ exports[`Upload Items Pane should render Default properly 1`] = `
"title": [Function],
"visible": [Function],
},
UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
@@ -1016,23 +999,6 @@ exports[`Upload Items Pane should render Default properly 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
}
}
formError=""

View File

@@ -411,23 +411,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"title": [Function],
"visible": [Function],
},
UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
@@ -1021,23 +1004,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
"files": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "uploadfilepane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"onImportLinkKeyPress": [Function],
"selectFileInputLabel": [Function],
"selectedFilesTitle": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
}
}
openNotificationConsole={[Function]}