diff --git a/src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx b/src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx index d1994e98b..69f8be8f2 100644 --- a/src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx +++ b/src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx @@ -3,7 +3,13 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; import { Link } from "office-ui-fabric-react/lib/Link"; -import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react"; +import { + ChoiceGroup, + FontIcon, + IChoiceGroupProps, + IProgressIndicatorProps, + ProgressIndicator, +} from "office-ui-fabric-react"; export interface TextFieldProps extends ITextFieldProps { label: string; @@ -27,6 +33,7 @@ export interface DialogProps { choiceGroupProps?: IChoiceGroupProps; textFieldProps?: TextFieldProps; linkProps?: LinkProps; + progressIndicatorProps?: IProgressIndicatorProps; primaryButtonText: string; secondaryButtonText: string; onPrimaryButtonClick: () => void; @@ -69,6 +76,7 @@ export class DialogComponent extends React.Component { const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps; const textFieldProps: ITextFieldProps = this.props.textFieldProps; const linkProps: LinkProps = this.props.linkProps; + const progressIndicatorProps: IProgressIndicatorProps = this.props.progressIndicatorProps; const primaryButtonProps: IButtonProps = { text: this.props.primaryButtonText, disabled: this.props.primaryButtonDisabled || false, @@ -91,6 +99,7 @@ export class DialogComponent extends React.Component { {linkProps.linkText} )} + {progressIndicatorProps && } {secondaryButtonProps && } diff --git a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx index 37c976668..64268ec20 100644 --- a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx +++ b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx @@ -127,7 +127,7 @@ export class GalleryCardComponent extends React.Component ; constructor(private props: GalleryAndNotebookViewerComponentProps) { + this.reset(); this.parameters = ko.observable(Date.now()); } public renderComponent(): JSX.Element { - return ; + return ; + } + + public reset(): void { + this.key = `GalleryAndNotebookViewerComponent-${Date.now()}`; } public triggerRender(): void { diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less index b0261f751..60e3180fb 100644 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less +++ b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less @@ -7,3 +7,12 @@ width: 100%; font-family: @DataExplorerFont; } + +.publicGalleryTabContainer { + position: relative; +} + +.publicGalleryTabOverlayContent { + background: white; + padding: 20px; +} diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx index addc71223..5fb919c41 100644 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx +++ b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx @@ -10,6 +10,7 @@ import { IRectangle, Label, List, + Overlay, Pivot, PivotItem, SearchBox, @@ -74,14 +75,14 @@ interface GalleryTabInfo { export class GalleryViewerComponent extends React.Component { public static readonly OfficialSamplesTitle = "Official samples"; public static readonly PublicGalleryTitle = "Public gallery"; - public static readonly FavoritesTitle = "Liked"; - public static readonly PublishedTitle = "Your published work"; + public static readonly FavoritesTitle = "My favorites"; + public static readonly PublishedTitle = "My published work"; private static readonly rowsPerPage = 5; private static readonly mostViewedText = "Most viewed"; private static readonly mostDownloadedText = "Most downloaded"; - private static readonly mostFavoritedText = "Most liked"; + private static readonly mostFavoritedText = "Most favorited"; private static readonly mostRecentText = "Most recent"; private readonly sortingOptions: IDropdownOption[]; @@ -222,8 +223,8 @@ export class GalleryViewerComponent extends React.Component { const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data); const content = ( - + {published?.length > 0 && this.createPublishedNotebooksSectionContent( undefined, @@ -276,7 +277,7 @@ export class GalleryViewerComponent extends React.Component { return ( - + {title && {title}} {description && {description}} {content} @@ -285,15 +286,22 @@ export class GalleryViewerComponent extends React.Component { - this.setState({ isCodeOfConductAccepted: result }); - }} - /> - ) : ( - this.createSearchBarHeader(this.createCardsTabContent(data)) + return ( +
+ {this.createSearchBarHeader(this.createCardsTabContent(data))} + {acceptedCodeOfConduct === false && ( + +
+ { + this.setState({ isCodeOfConductAccepted: result }); + }} + /> +
+
+ )} +
); } diff --git a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx index 6ad7d1e9b..4d7050790 100644 --- a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx +++ b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx @@ -3,14 +3,11 @@ */ import { Notebook } from "@nteract/commutable"; import { createContentRef } from "@nteract/core"; -import { IChoiceGroupProps, Icon, Link, ProgressIndicator } from "office-ui-fabric-react"; +import { IChoiceGroupProps, Icon, IProgressIndicatorProps, Link, ProgressIndicator } from "office-ui-fabric-react"; import * as React from "react"; import { contents } from "rx-jupyter"; -import * as Logger from "../../../Common/Logger"; import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient"; import * as GalleryUtils from "../../../Utils/GalleryUtils"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer"; @@ -21,7 +18,7 @@ import Explorer from "../../Explorer"; import { NotebookV4 } from "@nteract/commutable/lib/v4"; import { SessionStorageUtility } from "../../../Shared/StorageUtility"; import { DialogHost } from "../../../Utils/GalleryUtils"; -import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; +import { handleError } from "../../../Common/ErrorHandlingUtils"; export interface NotebookViewerComponentProps { container?: Explorer; @@ -178,6 +175,32 @@ export class NotebookViewerComponent }; } + // DialogHost + showOkModalDialog( + title: string, + msg: string, + okLabel: string, + onOk: () => void, + progressIndicatorProps?: IProgressIndicatorProps + ): void { + this.setState({ + dialogProps: { + isModal: true, + visible: true, + title, + subText: msg, + primaryButtonText: okLabel, + onPrimaryButtonClick: () => { + this.setState({ dialogProps: undefined }); + onOk && onOk(); + }, + secondaryButtonText: undefined, + onSecondaryButtonClick: undefined, + progressIndicatorProps, + }, + }); + } + // DialogHost showOkCancelModalDialog( title: string, @@ -186,8 +209,10 @@ export class NotebookViewerComponent onOk: () => void, cancelLabel: string, onCancel: () => void, + progressIndicatorProps?: IProgressIndicatorProps, choiceGroupProps?: IChoiceGroupProps, - textFieldProps?: TextFieldProps + textFieldProps?: TextFieldProps, + primaryButtonDisabled?: boolean ): void { this.setState({ dialogProps: { @@ -205,8 +230,10 @@ export class NotebookViewerComponent this.setState({ dialogProps: undefined }); onCancel && onCancel(); }, + progressIndicatorProps, choiceGroupProps, textFieldProps, + primaryButtonDisabled, }, }); } diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 2a1f25286..0ff71ab24 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -91,6 +91,7 @@ import { appInsights } from "../Shared/appInsights"; import { SelfServeLoadingComponentAdapter } from "../SelfServe/SelfServeLoadingComponentAdapter"; import { SelfServeType } from "../SelfServe/SelfServeUtils"; import { SelfServeComponentAdapter } from "../SelfServe/SelfServeComponentAdapter"; +import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent"; BindingHandlersRegisterer.registerBindingHandlers(); // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import @@ -2810,10 +2811,36 @@ export default class Explorer { } } - public async openGallery(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) { + public async openGallery( + selectedTab?: GalleryTab, + notebookUrl?: string, + galleryItem?: IGalleryItem, + isFavorite?: boolean + ) { let title: string = "Gallery"; let hashLocation: string = "gallery"; + const galleryTabOptions: any = { + // GalleryTabOptions + account: userContext.databaseAccount, + container: this, + junoClient: this.notebookManager?.junoClient, + selectedTab: selectedTab || GalleryTab.OfficialSamples, + notebookUrl, + galleryItem, + isFavorite, + // TabOptions + tabKind: ViewModels.CollectionTabKind.Gallery, + title: title, + tabPath: title, + documentClientUtility: null, + isActive: ko.observable(false), + hashLocation: hashLocation, + onUpdateTabsButtons: this.onUpdateTabsButtons, + isTabsContentExpanded: ko.observable(true), + onLoadStartKey: null, + }; + const galleryTabs = this.tabsManager.getTabs( ViewModels.CollectionTabKind.Gallery, (tab) => tab.hashLocation() == hashLocation @@ -2822,31 +2849,12 @@ export default class Explorer { if (galleryTab) { this.tabsManager.activateTab(galleryTab); + (galleryTab as any).reset(galleryTabOptions); } else { if (!this.galleryTab) { this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab"); } - - const newTab = new this.galleryTab.default({ - // GalleryTabOptions - account: userContext.databaseAccount, - container: this, - junoClient: this.notebookManager?.junoClient, - notebookUrl, - galleryItem, - isFavorite, - // TabOptions - tabKind: ViewModels.CollectionTabKind.Gallery, - title: title, - tabPath: title, - documentClientUtility: null, - isActive: ko.observable(false), - hashLocation: hashLocation, - onUpdateTabsButtons: this.onUpdateTabsButtons, - isTabsContentExpanded: ko.observable(true), - onLoadStartKey: null, - }); - + const newTab = new this.galleryTab.default(galleryTabOptions); this.tabsManager.activateNewTab(newTab); } } diff --git a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx index 55a92fb84..79f83e5a8 100644 --- a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx +++ b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx @@ -11,6 +11,7 @@ import { toJS } from "@nteract/commutable"; import { CodeOfConductComponent } from "../Controls/NotebookGallery/CodeOfConductComponent"; import { HttpStatusCodes } from "../../Common/Constants"; import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils"; +import { GalleryTab } from "../Controls/NotebookGallery/GalleryViewerComponent"; export class PublishNotebookPaneAdapter implements ReactAdapter { parameters: ko.Observable; @@ -163,6 +164,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter { ); } else { NotificationConsoleUtils.logConsoleInfo(`Published ${this.name} to gallery`); + this.container.openGallery(GalleryTab.Published); } } } catch (error) { diff --git a/src/Explorer/Panes/PublishNotebookPaneComponent.tsx b/src/Explorer/Panes/PublishNotebookPaneComponent.tsx index a0a81abb7..40d97fdd4 100644 --- a/src/Explorer/Panes/PublishNotebookPaneComponent.tsx +++ b/src/Explorer/Panes/PublishNotebookPaneComponent.tsx @@ -172,11 +172,12 @@ export class PublishNotebookPaneComponent extends React.Component { - this.props.onChangeName(newValue); - this.setState({ notebookName: newValue }); + const notebookName = newValue + ".ipynb"; + this.props.onChangeName(notebookName); + this.setState({ notebookName }); }, }; diff --git a/src/Explorer/Panes/__snapshots__/PublishNotebookPaneComponent.test.tsx.snap b/src/Explorer/Panes/__snapshots__/PublishNotebookPaneComponent.test.tsx.snap index a5efc18d7..18f57e35a 100644 --- a/src/Explorer/Panes/__snapshots__/PublishNotebookPaneComponent.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/PublishNotebookPaneComponent.test.tsx.snap @@ -25,7 +25,7 @@ exports[`PublishNotebookPaneComponent renders 1`] = ` void, + progressIndicatorProps?: IProgressIndicatorProps + ): void; + showOkCancelModalDialog( title: string, msg: string, @@ -88,8 +96,10 @@ export interface DialogHost { onOk: () => void, cancelLabel: string, onCancel: () => void, + progressIndicatorProps?: IProgressIndicatorProps, choiceGroupProps?: IChoiceGroupProps, - textFieldProps?: TextFieldProps + textFieldProps?: TextFieldProps, + primaryButtonDisabled?: boolean ): void; } @@ -108,8 +118,17 @@ export function reportAbuse( undefined, "Report Abuse", async () => { - const clearSubmitReportNotification = NotificationConsoleUtils.logConsoleProgress( - `Submitting your report on ${data.name} violating code of conduct` + dialogHost.showOkCancelModalDialog( + "Report Abuse", + `Submitting your report on ${data.name} violating code of conduct`, + "Reporting...", + undefined, + "Cancel", + undefined, + {}, + undefined, + undefined, + true ); try { @@ -118,9 +137,16 @@ export function reportAbuse( throw new Error(`Received HTTP ${response.status} when submitting report for ${data.name}`); } - NotificationConsoleUtils.logConsoleInfo( - `Your report on ${data.name} has been submitted. Thank you for reporting the violation.` + dialogHost.showOkModalDialog( + "Report Abuse", + `Your report on ${data.name} has been submitted. Thank you for reporting the violation.`, + "OK", + undefined, + { + percentComplete: 1, + } ); + onComplete(response.data); } catch (error) { handleError( @@ -128,12 +154,21 @@ export function reportAbuse( "GalleryUtils/reportAbuse", `Failed to submit report on ${data.name} violating code of conduct` ); - } - clearSubmitReportNotification(); + dialogHost.showOkModalDialog( + "Report Abuse", + `Failed to submit report on ${data.name} violating code of conduct`, + "OK", + undefined, + { + percentComplete: 1, + } + ); + } }, "Cancel", undefined, + undefined, { label: "How does this content violate the code of conduct?", options: abuseCategories,