mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-06-05 15:12:04 +01:00
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:
parent
4127d0f522
commit
b8e9903287
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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 => {
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user