Added spinner for notebook delete (#458)

* initial UI for delete published nb spinner

* added notebook delete spinner

* addressed PR comments
This commit is contained in:
Srinath Narayanan 2021-03-02 13:59:10 -08:00 committed by GitHub
parent 4127d0f522
commit b8e9903287
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 89 deletions

View File

@ -13,6 +13,8 @@ import {
LinkBase, LinkBase,
Separator, Separator,
TooltipHost, TooltipHost,
Spinner,
SpinnerSize,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient"; import { IGalleryItem } from "../../../../Juno/JunoClient";
@ -29,10 +31,14 @@ export interface GalleryCardComponentProps {
onFavoriteClick: () => void; onFavoriteClick: () => void;
onUnfavoriteClick: () => void; onUnfavoriteClick: () => void;
onDownloadClick: () => void; onDownloadClick: () => void;
onDeleteClick: () => void; onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void;
} }
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> { interface GalleryCardComponentState {
isDeletingPublishedNotebook: boolean;
}
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps, GalleryCardComponentState> {
public static readonly CARD_WIDTH = 256; public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144; private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio = public static readonly cardHeightToWidthRatio =
@ -40,6 +46,14 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private static readonly cardDescriptionMaxChars = 80; private static readonly cardDescriptionMaxChars = 80;
private static readonly cardItemGapBig = 10; private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8; private static readonly cardItemGapSmall = 8;
private static readonly cardDeleteSpinnerHeight = 360;
constructor(props: GalleryCardComponentProps) {
super(props);
this.state = {
isDeletingPublishedNotebook: false,
};
}
public render(): JSX.Element { public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete; const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
@ -59,91 +73,110 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }} tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => this.onClick(event, this.props.onClick)} onClick={(event) => this.onClick(event, this.props.onClick)}
> >
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}> {this.state.isDeletingPublishedNotebook && (
<Persona <Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
imageUrl={this.props.data.isSample && CosmosDBLogo} <Spinner
text={this.props.data.author} size={SpinnerSize.large}
secondaryText={dateString} label={`Deleting '${cardTitle}'`}
/> styles={{ root: { height: GalleryCardComponent.cardDeleteSpinnerHeight } }}
</Card.Item> />
</Card.Item>
)}
{!this.state.isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
<Card.Item> <Card.Item>
<Image <Image
src={this.props.data.thumbnailUrl} src={this.props.data.thumbnailUrl}
width={GalleryCardComponent.CARD_WIDTH} width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight} height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover} imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`} alt={`${cardTitle} cover image`}
/> />
</Card.Item> </Card.Item>
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}> <Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap> <Text variant="small" nowrap>
{this.props.data.tags ? ( {this.props.data.tags ? (
this.props.data.tags.map((tag, index, array) => ( this.props.data.tags.map((tag, index, array) => (
<span key={tag}> <span key={tag}>
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link> <Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "} {index === array.length - 1 ? <></> : ", "}
</span> </span>
)) ))
) : ( ) : (
<br /> <br />
)}
</Text>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: 36 } }}>
{this.renderTruncatedDescription()}
</Text>
<span>
{this.props.data.views !== undefined && this.generateIconText("RedEye", this.props.data.views.toString())}
{this.props.data.downloads !== undefined &&
this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.data.favorites !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unfavorite" : "Favorite",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)} )}
</Text>
{this.props.showDownload && <Text
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)} styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
{this.props.showDelete && <Text variant="small" styles={{ root: { height: 36 } }}>
this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)} {this.renderTruncatedDescription()}
</span> </Text>
</Card.Section>
<span>
{this.props.data.views !== undefined &&
this.generateIconText("RedEye", this.props.data.views.toString())}
{this.props.data.downloads !== undefined &&
this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.data.favorites !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unfavorite" : "Favorite",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)}
{this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
this.props.onDeleteClick(
() => this.setState({ isDeletingPublishedNotebook: true }),
() => this.setState({ isDeletingPublishedNotebook: false })
)
)}
</span>
</Card.Section>
)}
</>
)} )}
</Card> </Card>
); );

View File

@ -666,7 +666,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
onFavoriteClick: () => this.favoriteItem(data), onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data), onUnfavoriteClick: () => this.unfavoriteItem(data),
onDownloadClick: () => this.downloadItem(data), onDownloadClick: () => this.downloadItem(data),
onDeleteClick: () => this.deleteItem(data), onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) =>
this.deleteItem(data, beforeDelete, afterDelete),
}; };
return ( return (
@ -710,11 +711,18 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
}; };
private deleteItem = async (data: IGalleryItem): Promise<void> => { private deleteItem = async (data: IGalleryItem, beforeDelete: () => void, afterDelete: () => void): Promise<void> => {
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, (item) => { GalleryUtils.deleteItem(
this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id); this.props.container,
this.refreshSelectedTab(item); this.props.junoClient,
}); data,
(item) => {
this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id);
this.refreshSelectedTab(item);
},
beforeDelete,
afterDelete
);
}; };
private onPivotChange = (item: PivotItem): void => { private onPivotChange = (item: PivotItem): void => {

View File

@ -373,7 +373,9 @@ export function deleteItem(
container: Explorer, container: Explorer,
junoClient: JunoClient, junoClient: JunoClient,
data: IGalleryItem, data: IGalleryItem,
onComplete: (item: IGalleryItem) => void onComplete: (item: IGalleryItem) => void,
beforeDelete?: () => void,
afterDelete?: () => void
): void { ): void {
if (container) { if (container) {
trace(Action.NotebooksGalleryClickDelete, ActionModifiers.Mark, { notebookId: data.id }); trace(Action.NotebooksGalleryClickDelete, ActionModifiers.Mark, { notebookId: data.id });
@ -383,6 +385,9 @@ export function deleteItem(
`Would you like to remove ${data.name} from the gallery?`, `Would you like to remove ${data.name} from the gallery?`,
"Remove", "Remove",
async () => { async () => {
if (beforeDelete) {
beforeDelete();
}
const name = data.name; const name = data.name;
const notificationId = NotificationConsoleUtils.logConsoleMessage( const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
@ -409,6 +414,10 @@ export function deleteItem(
); );
handleError(error, "GalleryUtils/deleteItem", `Failed to remove ${name} from gallery`); handleError(error, "GalleryUtils/deleteItem", `Failed to remove ${name} from gallery`);
} finally {
if (afterDelete) {
afterDelete();
}
} }
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);