Updates to standalone gallery (#91)

* Add hyperlink icon for links in DialogComponent

* Use a Cosmos DB image for sample Persona

* Fix tests

* Update standalone gallery behavior to match DE gallery

* Use /gallery endpoint for gallery

* Update Gallery Card style to remove buttons in standalone mode

* Address comments

* Add text banner
This commit is contained in:
Tanuj Mittal 2020-07-15 13:58:43 -07:00 committed by GitHub
parent 543ae9fe4a
commit b1e20796c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 484 additions and 536 deletions

View File

@ -3,6 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link"; import { Link } from "office-ui-fabric-react/lib/Link";
import { FontIcon } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
label: string; label: string;
@ -84,7 +85,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
{textFieldProps && <TextField {...textFieldProps} />} {textFieldProps && <TextField {...textFieldProps} />}
{linkProps && ( {linkProps && (
<Link href={linkProps.linkUrl} target="_blank"> <Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} {linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link> </Link>
)} )}
<DialogFooter> <DialogFooter>

View File

@ -5,11 +5,9 @@ export class GalleryHeaderComponent extends React.Component {
private static readonly azureText = "Microsoft Azure"; private static readonly azureText = "Microsoft Azure";
private static readonly cosmosdbText = "Cosmos DB"; private static readonly cosmosdbText = "Cosmos DB";
private static readonly galleryText = "Gallery"; private static readonly galleryText = "Gallery";
private static readonly loginText = "Log in"; private static readonly loginText = "Sign In";
private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank"); private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank");
private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href); private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href);
private static readonly openGallery = () =>
(window.location.href = new URL("./galleryViewer.html", window.location.href).href);
private static readonly headerItemStyle: React.CSSProperties = { private static readonly headerItemStyle: React.CSSProperties = {
color: "white" color: "white"
}; };
@ -63,7 +61,7 @@ export class GalleryHeaderComponent extends React.Component {
<Stack.Item> <Stack.Item>
{this.renderHeaderItem( {this.renderHeaderItem(
GalleryHeaderComponent.galleryText, GalleryHeaderComponent.galleryText,
GalleryHeaderComponent.openGallery, undefined,
GalleryHeaderComponent.headerItemTextProps GalleryHeaderComponent.headerItemTextProps
)} )}
</Stack.Item> </Stack.Item>

View File

@ -20,6 +20,7 @@ describe("GalleryCardComponent", () => {
views: 0 views: 0
}, },
isFavorite: false, isFavorite: false,
showDownload: true,
showDelete: true, showDelete: true,
onClick: undefined, onClick: undefined,
onTagClick: undefined, onTagClick: undefined,

View File

@ -1,4 +1,4 @@
import { Card, ICardTokens } from "@uifabric/react-cards"; import { Card } from "@uifabric/react-cards";
import { import {
FontWeights, FontWeights,
Icon, Icon,
@ -18,10 +18,12 @@ import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient"; import { IGalleryItem } from "../../../../Juno/JunoClient";
import { FileSystemUtil } from "../../../Notebook/FileSystemUtil"; import { FileSystemUtil } from "../../../Notebook/FileSystemUtil";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg"; import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { StyleConstants } from "../../../../Common/Constants";
export interface GalleryCardComponentProps { export interface GalleryCardComponentProps {
data: IGalleryItem; data: IGalleryItem;
isFavorite: boolean; isFavorite: boolean;
showDownload: boolean;
showDelete: boolean; showDelete: boolean;
onClick: () => void; onClick: () => void;
onTagClick: (tag: string) => void; onTagClick: (tag: string) => void;
@ -32,30 +34,30 @@ export interface GalleryCardComponentProps {
} }
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> { export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> {
public static readonly CARD_HEIGHT = 384;
public static readonly CARD_WIDTH = 256; public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144; private static readonly cardImageHeight = 144;
private static readonly cardDescriptionMaxChars = 88; private static readonly cardDescriptionMaxChars = 88;
private static readonly cardTokens: ICardTokens = { private static readonly cardItemGapBig = 10;
width: GalleryCardComponent.CARD_WIDTH, private static readonly cardItemGapSmall = 8;
height: GalleryCardComponent.CARD_HEIGHT,
childrenGap: 8,
childrenMargin: 10
};
public render(): JSX.Element { public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
const options: Intl.DateTimeFormatOptions = { const options: Intl.DateTimeFormatOptions = {
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric" day: "numeric"
}; };
const dateString = new Date(this.props.data.created).toLocaleString("default", options); const dateString = new Date(this.props.data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
return ( return (
<Card aria-label="Notebook Card" tokens={GalleryCardComponent.cardTokens} onClick={this.props.onClick}> <Card
<Card.Item> aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={event => this.onClick(event, this.props.onClick)}
>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona <Persona
imageUrl={this.props.data.isSample && CosmosDBLogo} imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author} text={this.props.data.author}
@ -63,69 +65,89 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
/> />
</Card.Item> </Card.Item>
<Card.Item fill> <Card.Item>
<Image <Image
src={ src={this.props.data.thumbnailUrl}
this.props.data.thumbnailUrl ||
`https://placehold.it/${GalleryCardComponent.CARD_WIDTH}x${GalleryCardComponent.cardImageHeight}`
}
width={GalleryCardComponent.CARD_WIDTH} width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight} height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover} imageFit={ImageFit.cover}
alt="Notebook cover image" alt={`${cardTitle} cover image`}
/> />
</Card.Item> </Card.Item>
<Card.Section> <Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap> <Text variant="small" nowrap>
{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): void => this.onTagClick(event, 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>
))} ))}
</Text> </Text>
<Text styles={{ root: { fontWeight: FontWeights.semibold } }} nowrap>
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")} <Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall
}
}}
nowrap
>
{cardTitle}
</Text> </Text>
<Text variant="small" styles={{ root: { height: 36 } }}> <Text variant="small" styles={{ root: { height: 36 } }}>
{this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars)} {this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars)}
</Text> </Text>
</Card.Section>
<Card.Section horizontal styles={{ root: { alignItems: "flex-end" } }}> <span>
{this.generateIconText("RedEye", this.props.data.views.toString())} {this.generateIconText("RedEye", this.props.data.views.toString())}
{this.generateIconText("Download", this.props.data.downloads.toString())} {this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.isFavorite !== undefined && this.generateIconText("Heart", this.props.data.favorites.toString())} {this.props.isFavorite !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section> </Card.Section>
<Card.Item> {cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig
}
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} /> <Separator styles={{ root: { padding: 0, height: 1 } }} />
</Card.Item>
<Card.Section horizontal styles={{ root: { marginTop: 0 } }}> <span>
{this.props.isFavorite !== undefined && {this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip( this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart", this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unlike" : "Like", this.props.isFavorite ? "Unlike" : "Like",
this.props.isFavorite ? this.onUnfavoriteClick : this.onFavoriteClick "left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)} )}
{this.generateIconButtonWithTooltip("Download", "Download", this.onDownloadClick)} {this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.props.showDelete && ( {this.props.showDelete &&
<div style={{ width: "100%", textAlign: "right" }}> this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)}
{this.generateIconButtonWithTooltip("Delete", "Remove", this.onDeleteClick)} </span>
</div>
)}
</Card.Section> </Card.Section>
)}
</Card> </Card>
); );
} }
private generateIconText = (iconName: string, text: string): JSX.Element => { private generateIconText = (iconName: string, text: string): JSX.Element => {
return ( return (
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}> <Text
variant="tiny"
styles={{ root: { color: StyleConstants.BaseMedium, paddingRight: GalleryCardComponent.cardItemGapSmall } }}
>
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text} <Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
</Text> </Text>
); );
@ -138,70 +160,37 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private generateIconButtonWithTooltip = ( private generateIconButtonWithTooltip = (
iconName: string, iconName: string,
title: string, title: string,
onClick: ( horizontalAlign: "right" | "left",
event: React.MouseEvent< activate: () => void
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
) => void
): JSX.Element => { ): JSX.Element => {
return ( return (
<TooltipHost <TooltipHost
content={title} content={title}
id={`TooltipHost-IconButton-${iconName}`} id={`TooltipHost-IconButton-${iconName}`}
calloutProps={{ gapSpace: 0 }} calloutProps={{ gapSpace: 0 }}
styles={{ root: { display: "inline-block" } }} styles={{ root: { display: "inline-block", float: horizontalAlign } }}
> >
<IconButton iconProps={{ iconName }} title={title} ariaLabel={title} onClick={onClick} /> <IconButton
iconProps={{ iconName }}
title={title}
ariaLabel={title}
onClick={event => this.onClick(event, activate)}
/>
</TooltipHost> </TooltipHost>
); );
}; };
private onTagClick = ( private onClick = (
event: React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>, event:
tag: string | React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>
): void => { | React.MouseEvent<
event.stopPropagation();
this.props.onTagClick(tag);
};
private onFavoriteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement, HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent MouseEvent
> >,
activate: () => void
): void => { ): void => {
event.stopPropagation(); event.stopPropagation();
this.props.onFavoriteClick(); event.preventDefault();
}; activate();
private onUnfavoriteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onUnfavoriteClick();
};
private onDownloadClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onDownloadClick();
};
private onDeleteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onDeleteClick();
}; };
} }

View File

@ -2,35 +2,47 @@
exports[`GalleryCardComponent renders 1`] = ` exports[`GalleryCardComponent renders 1`] = `
<Card <Card
aria-label="Notebook Card" aria-label="name"
data-is-focusable="true"
onClick={[Function]}
tokens={ tokens={
Object { Object {
"childrenGap": 8, "childrenGap": 0,
"childrenMargin": 10,
"height": 384,
"width": 256, "width": 256,
} }
} }
> >
<CardItem> <CardItem
tokens={
Object {
"padding": 10,
}
}
>
<StyledPersonaBase <StyledPersonaBase
imageUrl={false} imageUrl={false}
secondaryText="Invalid Date" secondaryText="Invalid Date"
text="author" text="author"
/> />
</CardItem> </CardItem>
<CardItem <CardItem>
fill={true}
>
<Memo(StyledImageBase) <Memo(StyledImageBase)
alt="Notebook cover image" alt="name cover image"
height={144} height={144}
imageFit={2} imageFit={2}
src="thumbnailUrl" src="thumbnailUrl"
width={256} width={256}
/> />
</CardItem> </CardItem>
<CardSection> <CardSection
styles={
Object {
"root": Object {
"padding": 10,
},
}
}
>
<Text <Text
nowrap={true} nowrap={true}
variant="small" variant="small"
@ -51,6 +63,8 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"fontWeight": 600, "fontWeight": 600,
"paddingBottom": 8,
"paddingTop": 8,
}, },
} }
} }
@ -69,22 +83,13 @@ exports[`GalleryCardComponent renders 1`] = `
> >
description description
</Text> </Text>
</CardSection> <span>
<CardSection
horizontal={true}
styles={
Object {
"root": Object {
"alignItems": "flex-end",
},
}
}
>
<Text <Text
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "#ccc", "color": undefined,
"paddingRight": 8,
}, },
} }
} }
@ -107,7 +112,8 @@ exports[`GalleryCardComponent renders 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "#ccc", "color": undefined,
"paddingRight": 8,
}, },
} }
} }
@ -130,7 +136,8 @@ exports[`GalleryCardComponent renders 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "#ccc", "color": undefined,
"paddingRight": 8,
}, },
} }
} }
@ -149,8 +156,18 @@ exports[`GalleryCardComponent renders 1`] = `
0 0
</Text> </Text>
</span>
</CardSection> </CardSection>
<CardItem> <CardSection
styles={
Object {
"root": Object {
"marginLeft": 10,
"marginRight": 10,
},
}
}
>
<Styled <Styled
styles={ styles={
Object { Object {
@ -161,17 +178,7 @@ exports[`GalleryCardComponent renders 1`] = `
} }
} }
/> />
</CardItem> <span>
<CardSection
horizontal={true}
styles={
Object {
"root": Object {
"marginTop": 0,
},
}
}
>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { Object {
@ -184,6 +191,7 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "left",
}, },
} }
} }
@ -211,6 +219,7 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "left",
}, },
} }
} }
@ -226,14 +235,6 @@ exports[`GalleryCardComponent renders 1`] = `
title="Download" title="Download"
/> />
</StyledTooltipHostBase> </StyledTooltipHostBase>
<div
style={
Object {
"textAlign": "right",
"width": "100%",
}
}
>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { Object {
@ -246,6 +247,7 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "right",
}, },
} }
} }
@ -261,7 +263,7 @@ exports[`GalleryCardComponent renders 1`] = `
title="Remove" title="Remove"
/> />
</StyledTooltipHostBase> </StyledTooltipHostBase>
</div> </span>
</CardSection> </CardSection>
</Card> </Card>
`; `;

View File

@ -0,0 +1,114 @@
import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient";
import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent";
import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
export interface GalleryAndNotebookViewerComponentProps {
container?: ViewModels.Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
interface GalleryAndNotebookViewerComponentState {
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
export class GalleryAndNotebookViewerComponent extends React.Component<
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponentState
> {
constructor(props: GalleryAndNotebookViewerComponentProps) {
super(props);
this.state = {
notebookUrl: props.notebookUrl,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
selectedTab: props.selectedTab,
sortBy: props.sortBy,
searchText: props.searchText
};
}
public render(): JSX.Element {
if (this.state.notebookUrl) {
const props: NotebookViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
notebookUrl: this.state.notebookUrl,
galleryItem: this.state.galleryItem,
isFavorite: this.state.isFavorite,
backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab),
onBackClick: this.onBackClick,
onTagClick: this.loadTaggedItems
};
return <NotebookViewerComponent {...props} />;
}
const props: GalleryViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy,
searchText: this.state.searchText,
openNotebook: this.openNotebook,
onSelectedTabChange: this.onSelectedTabChange,
onSortByChange: this.onSortByChange,
onSearchTextChange: this.onSearchTextChange
};
return <GalleryViewerComponent {...props} />;
}
private onBackClick = (): void => {
this.setState({
notebookUrl: undefined
});
};
private loadTaggedItems = (tag: string): void => {
this.setState({
notebookUrl: undefined,
searchText: tag
});
};
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
this.setState({
notebookUrl: this.props.junoClient.getNotebookContentUrl(data.id),
galleryItem: data,
isFavorite
});
};
private onSelectedTabChange = (selectedTab: GalleryTab): void => {
this.setState({
selectedTab
});
};
private onSortByChange = (sortBy: SortBy): void => {
this.setState({
sortBy
});
};
private onSearchTextChange = (searchText: string): void => {
this.setState({
searchText
});
};
}

View File

@ -0,0 +1,23 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import {
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponent
} from "./GalleryAndNotebookViewerComponent";
export class GalleryAndNotebookViewerComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GalleryAndNotebookViewerComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GalleryAndNotebookViewerComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@ -9,6 +9,7 @@ describe("GalleryViewerComponent", () => {
selectedTab: GalleryTab.OfficialSamples, selectedTab: GalleryTab.OfficialSamples,
sortBy: SortBy.MostViewed, sortBy: SortBy.MostViewed,
searchText: undefined, searchText: undefined,
openNotebook: undefined,
onSelectedTabChange: undefined, onSelectedTabChange: undefined,
onSortByChange: undefined, onSortByChange: undefined,
onSearchTextChange: undefined onSearchTextChange: undefined

View File

@ -31,6 +31,7 @@ export interface GalleryViewerComponentProps {
selectedTab: GalleryTab; selectedTab: GalleryTab;
sortBy: SortBy; sortBy: SortBy;
searchText: string; searchText: string;
openNotebook: (data: IGalleryItem, isFavorite: boolean) => void;
onSelectedTabChange: (newTab: GalleryTab) => void; onSelectedTabChange: (newTab: GalleryTab) => void;
onSortByChange: (sortBy: SortBy) => void; onSortByChange: (sortBy: SortBy) => void;
onSearchTextChange: (searchText: string) => void; onSearchTextChange: (searchText: string) => void;
@ -66,13 +67,14 @@ interface GalleryTabInfo {
content: JSX.Element; content: JSX.Element;
} }
export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState> export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState> {
implements GalleryUtils.DialogEnabledComponent {
public static readonly OfficialSamplesTitle = "Official samples"; public static readonly OfficialSamplesTitle = "Official samples";
public static readonly PublicGalleryTitle = "Public gallery"; public static readonly PublicGalleryTitle = "Public gallery";
public static readonly FavoritesTitle = "Liked"; public static readonly FavoritesTitle = "Liked";
public static readonly PublishedTitle = "Your published work"; public static readonly PublishedTitle = "Your published work";
private static readonly rowsPerPage = 5;
private static readonly mostViewedText = "Most viewed"; private static readonly mostViewedText = "Most viewed";
private static readonly mostDownloadedText = "Most downloaded"; private static readonly mostDownloadedText = "Most downloaded";
private static readonly mostFavoritedText = "Most liked"; private static readonly mostFavoritedText = "Most liked";
@ -128,10 +130,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
} }
} }
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
public render(): JSX.Element { public render(): JSX.Element {
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)]; const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
@ -178,8 +176,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createTabContent(data: IGalleryItem[]): JSX.Element { private createTabContent(data: IGalleryItem[]): JSX.Element {
return ( return (
<Stack tokens={{ childrenGap: 20 }}> <Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal tokens={{ childrenGap: 20 }}> <Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
<Stack.Item grow> <Stack.Item grow>
<SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} /> <SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} />
</Stack.Item> </Stack.Item>
@ -391,7 +389,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => { private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
if (itemIndex === 0) { if (itemIndex === 0) {
this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH) || this.columnCount; this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH) || this.columnCount;
this.rowCount = Math.ceil(visibleRect.height / GalleryCardComponent.CARD_HEIGHT) || this.rowCount; this.rowCount = GalleryViewerComponent.rowsPerPage;
} }
return { return {
@ -408,8 +406,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
const props: GalleryCardComponentProps = { const props: GalleryCardComponentProps = {
data, data,
isFavorite, isFavorite,
showDownload: !!this.props.container,
showDelete: this.state.selectedTab === GalleryTab.Published, showDelete: this.state.selectedTab === GalleryTab.Published,
onClick: () => this.openNotebook(data, isFavorite), onClick: () => this.props.openNotebook(data, isFavorite),
onTagClick: this.loadTaggedItems, onTagClick: this.loadTaggedItems,
onFavoriteClick: () => this.favoriteItem(data), onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data), onUnfavoriteClick: () => this.unfavoriteItem(data),
@ -424,20 +423,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
}; };
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
if (this.props.container && this.props.junoClient) {
this.props.container.openGallery(this.props.junoClient.getNotebookContentUrl(data.id), data, isFavorite);
} else {
const params = new URLSearchParams({
[GalleryUtils.NotebookViewerParams.NotebookUrl]: this.props.junoClient.getNotebookContentUrl(data.id),
[GalleryUtils.NotebookViewerParams.GalleryItemId]: data.id
});
const location = new URL("./notebookViewer.html", window.location.href).href;
window.open(`${location}?${params.toString()}`);
}
};
private loadTaggedItems = (tag: string): void => { private loadTaggedItems = (tag: string): void => {
const searchText = tag; const searchText = tag;
this.setState({ this.setState({
@ -467,9 +452,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}; };
private downloadItem = async (data: IGalleryItem): Promise<void> => { private downloadItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.downloadItem(this, this.props.container, this.props.junoClient, data, item => GalleryUtils.downloadItem(this.props.container, this.props.junoClient, data, item => this.refreshSelectedTab(item));
this.refreshSelectedTab(item)
);
}; };
private deleteItem = async (data: IGalleryItem): Promise<void> => { private deleteItem = async (data: IGalleryItem): Promise<void> => {

View File

@ -21,7 +21,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { Object {
"childrenGap": 20, "childrenGap": 10,
} }
} }
> >
@ -30,6 +30,7 @@ exports[`GalleryViewerComponent renders 1`] = `
tokens={ tokens={
Object { Object {
"childrenGap": 20, "childrenGap": 20,
"padding": 10,
} }
} }
> >

View File

@ -16,11 +16,12 @@ import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient"; import { IGalleryItem } from "../../../Juno/JunoClient";
import { FileSystemUtil } from "../../Notebook/FileSystemUtil"; import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
export interface NotebookMetadataComponentProps { export interface NotebookMetadataComponentProps {
data: IGalleryItem; data: IGalleryItem;
isFavorite: boolean; isFavorite: boolean;
downloadButtonText: string; downloadButtonText?: string;
onTagClick: (tag: string) => void; onTagClick: (tag: string) => void;
onFavoriteClick: () => void; onFavoriteClick: () => void;
onUnfavoriteClick: () => void; onUnfavoriteClick: () => void;
@ -54,11 +55,18 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
</> </>
)} )}
</Text> </Text>
{this.props.downloadButtonText && (
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} /> <PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
)}
</Stack> </Stack>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
<Persona text={this.props.data.author} size={PersonaSize.size32} /> <Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
size={PersonaSize.size32}
/>
<Text>{dateString}</Text> <Text>{dateString}</Text>
<Text> <Text>
<Icon iconName="RedEye" /> {this.props.data.views} <Icon iconName="RedEye" /> {this.props.data.views}

View File

@ -39,8 +39,10 @@ interface NotebookViewerComponentState {
showProgressBar: boolean; showProgressBar: boolean;
} }
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState> export class NotebookViewerComponent extends React.Component<
implements GalleryUtils.DialogEnabledComponent { NotebookViewerComponentProps,
NotebookViewerComponentState
> {
private clientManager: NotebookClientV2; private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper; private notebookComponentBootstrapper: NotebookComponentBootstrapper;
@ -73,10 +75,6 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
this.loadNotebookContent(); this.loadNotebookContent();
} }
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
private async loadNotebookContent(): Promise<void> { private async loadNotebookContent(): Promise<void> {
try { try {
const response = await fetch(this.props.notebookUrl); const response = await fetch(this.props.notebookUrl);
@ -121,9 +119,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<NotebookMetadataComponent <NotebookMetadataComponent
data={this.state.galleryItem} data={this.state.galleryItem}
isFavorite={this.state.isFavorite} isFavorite={this.state.isFavorite}
downloadButtonText={ downloadButtonText={this.props.container && "Download to my notebooks"}
this.props.container ? "Download to my notebooks" : "Edit/Run in Cosmos DB data explorer"
}
onTagClick={this.props.onTagClick} onTagClick={this.props.onTagClick}
onFavoriteClick={this.favoriteItem} onFavoriteClick={this.favoriteItem}
onUnfavoriteClick={this.unfavoriteItem} onUnfavoriteClick={this.unfavoriteItem}
@ -179,7 +175,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
}; };
private downloadItem = async (): Promise<void> => { private downloadItem = async (): Promise<void> => {
GalleryUtils.downloadItem(this, this.props.container, this.props.junoClient, this.state.galleryItem, item => GalleryUtils.downloadItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
this.setState({ galleryItem: item }) this.setState({ galleryItem: item })
); );
}; };

View File

@ -48,6 +48,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
verticalAlign="center" verticalAlign="center"
> >
<StyledPersonaBase <StyledPersonaBase
imageUrl={false}
size={11} size={11}
text="author" text="author"
/> />
@ -147,6 +148,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
verticalAlign="center" verticalAlign="center"
> >
<StyledPersonaBase <StyledPersonaBase
imageUrl={false}
size={11} size={11}
text="author" text="author"
/> />

View File

@ -3101,7 +3101,6 @@ export default class Explorer implements ViewModels.Explorer {
if (galleryTab) { if (galleryTab) {
this.tabsManager.activateTab(galleryTab); this.tabsManager.activateTab(galleryTab);
(galleryTab as any).updateGalleryParams(notebookUrl, galleryItem, isFavorite);
} else { } else {
if (!this.galleryTab) { if (!this.galleryTab) {
this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab"); this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab");

View File

@ -1 +1 @@
<div style="height: 100%" data-bind="react:galleryComponentAdapter, setTemplateReady: true"></div> <div style="height: 100%" data-bind="react:galleryAndNotebookViewerComponentAdapter, setTemplateReady: true"></div>

View File

@ -1,129 +1,21 @@
import * as ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../Juno/JunoClient"; import { GalleryAndNotebookViewerComponentProps } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponent";
import * as GalleryUtils from "../../Utils/GalleryUtils"; import { GalleryAndNotebookViewerComponentAdapter } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponentAdapter";
import { import { GalleryTab as GalleryViewerTab, SortBy } from "../Controls/NotebookGallery/GalleryViewerComponent";
GalleryTab as GalleryViewerTab,
GalleryViewerComponent,
GalleryViewerComponentProps,
SortBy
} from "../Controls/NotebookGallery/GalleryViewerComponent";
import {
NotebookViewerComponent,
NotebookViewerComponentProps
} from "../Controls/NotebookViewer/NotebookViewerComponent";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
/** /**
* Notebook gallery tab * Notebook gallery tab
*/ */
interface GalleryComponentAdapterProps {
container: ViewModels.Explorer;
junoClient: JunoClient;
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryViewerTab;
sortBy: SortBy;
searchText: string;
}
interface GalleryComponentAdapterState {
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryViewerTab;
sortBy: SortBy;
searchText: string;
}
class GalleryComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
private state: GalleryComponentAdapterState;
constructor(private props: GalleryComponentAdapterProps) {
this.parameters = ko.observable<number>(Date.now());
this.state = {
notebookUrl: props.notebookUrl,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
selectedTab: props.selectedTab,
sortBy: props.sortBy,
searchText: props.searchText
};
}
public renderComponent(): JSX.Element {
if (this.state.notebookUrl) {
const props: NotebookViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
notebookUrl: this.state.notebookUrl,
galleryItem: this.state.galleryItem,
isFavorite: this.state.isFavorite,
backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab),
onBackClick: this.onBackClick,
onTagClick: this.loadTaggedItems
};
return <NotebookViewerComponent {...props} />;
}
const props: GalleryViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy,
searchText: this.state.searchText,
onSelectedTabChange: this.onSelectedTabChange,
onSortByChange: this.onSortByChange,
onSearchTextChange: this.onSearchTextChange
};
return <GalleryViewerComponent {...props} />;
}
public setState(state: Partial<GalleryComponentAdapterState>): void {
this.state = Object.assign(this.state, state);
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
private onBackClick = (): void => {
this.props.container.openGallery();
};
private loadTaggedItems = (tag: string): void => {
this.setState({
notebookUrl: undefined,
searchText: tag
});
};
private onSelectedTabChange = (selectedTab: GalleryViewerTab): void => {
this.state.selectedTab = selectedTab;
};
private onSortByChange = (sortBy: SortBy): void => {
this.state.sortBy = sortBy;
};
private onSearchTextChange = (searchText: string): void => {
this.state.searchText = searchText;
};
}
export default class GalleryTab extends TabsBase implements ViewModels.Tab { export default class GalleryTab extends TabsBase implements ViewModels.Tab {
private container: ViewModels.Explorer; private container: ViewModels.Explorer;
private galleryComponentAdapterProps: GalleryComponentAdapterProps; public galleryAndNotebookViewerComponentAdapter: GalleryAndNotebookViewerComponentAdapter;
private galleryComponentAdapter: GalleryComponentAdapter;
constructor(options: ViewModels.GalleryTabOptions) { constructor(options: ViewModels.GalleryTabOptions) {
super(options); super(options);
this.container = options.container; this.container = options.container;
this.galleryComponentAdapterProps = { const props: GalleryAndNotebookViewerComponentProps = {
container: options.container, container: options.container,
junoClient: options.junoClient, junoClient: options.junoClient,
notebookUrl: options.notebookUrl, notebookUrl: options.notebookUrl,
@ -134,18 +26,10 @@ export default class GalleryTab extends TabsBase implements ViewModels.Tab {
searchText: undefined searchText: undefined
}; };
this.galleryComponentAdapter = new GalleryComponentAdapter(this.galleryComponentAdapterProps); this.galleryAndNotebookViewerComponentAdapter = new GalleryAndNotebookViewerComponentAdapter(props);
} }
protected getContainer(): ViewModels.Explorer { protected getContainer(): ViewModels.Explorer {
return this.container; return this.container;
} }
public updateGalleryParams(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean): void {
this.galleryComponentAdapter.setState({
notebookUrl,
galleryItem,
isFavorite
});
}
} }

View File

@ -1,31 +1,33 @@
import "bootstrap/dist/css/bootstrap.css"; import "bootstrap/dist/css/bootstrap.css";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons"; import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import { Text, Link } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import { initializeConfiguration } from "../Config"; import { initializeConfiguration } from "../Config";
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
import { import {
GalleryTab, GalleryAndNotebookViewerComponent,
GalleryViewerComponent, GalleryAndNotebookViewerComponentProps
GalleryViewerComponentProps, } from "../Explorer/Controls/NotebookGallery/GalleryAndNotebookViewerComponent";
SortBy import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
import { JunoClient } from "../Juno/JunoClient"; import { JunoClient } from "../Juno/JunoClient";
import * as GalleryUtils from "../Utils/GalleryUtils"; import * as GalleryUtils from "../Utils/GalleryUtils";
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
const enableNotebooksUrl = "https://aka.ms/cosmos-enable-notebooks";
const createAccountUrl = "https://aka.ms/cosmos-create-account-portal";
const onInit = async () => { const onInit = async () => {
const dataExplorerUrl = new URL("./", window.location.href).href;
initializeIcons(); initializeIcons();
await initializeConfiguration(); await initializeConfiguration();
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search); const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
const props: GalleryViewerComponentProps = { const props: GalleryAndNotebookViewerComponentProps = {
junoClient: new JunoClient(), junoClient: new JunoClient(),
selectedTab: galleryViewerProps.selectedTab || GalleryTab.OfficialSamples, selectedTab: galleryViewerProps.selectedTab || GalleryTab.OfficialSamples,
sortBy: galleryViewerProps.sortBy || SortBy.MostViewed, sortBy: galleryViewerProps.sortBy || SortBy.MostViewed,
searchText: galleryViewerProps.searchText, searchText: galleryViewerProps.searchText
onSelectedTabChange: undefined,
onSortByChange: undefined,
onSearchTextChange: undefined
}; };
const element = ( const element = (
@ -34,7 +36,21 @@ const onInit = async () => {
<GalleryHeaderComponent /> <GalleryHeaderComponent />
</header> </header>
<div style={{ marginLeft: 138, marginRight: 138 }}> <div style={{ marginLeft: 138, marginRight: 138 }}>
<GalleryViewerComponent {...props} /> <div style={{ paddingLeft: 26, paddingRight: 26, paddingTop: 20 }}>
<Text block>
Welcome to the Azure Cosmos DB notebooks gallery! View the sample notebooks to learn about use cases, best
practices, and how to get started with Azure Cosmos DB.
</Text>
<Text styles={{ root: { marginTop: 10 } }} block>
If you'd like to run or edit the notebook in your own Azure Cosmos DB account,{" "}
<Link href={dataExplorerUrl}>sign in</Link> and select an account with{" "}
<Link href={enableNotebooksUrl}>notebooks enabled</Link>. From there, you can download the sample to your
account. If you don't have an account yet, you can{" "}
<Link href={createAccountUrl}>create one from the Azure portal</Link>.
</Text>
</div>
<GalleryAndNotebookViewerComponent {...props} />
</div> </div>
</> </>
); );

View File

@ -7,7 +7,7 @@
<link rel="shortcut icon" href="images/CosmosDB_rgb_ui_lighttheme.ico" type="image/x-icon" /> <link rel="shortcut icon" href="images/CosmosDB_rgb_ui_lighttheme.ico" type="image/x-icon" />
</head> </head>
<body> <body style="overflow-y:scroll">
<div class="galleryContent" id="galleryContent"></div> <div class="galleryContent" id="galleryContent"></div>
</body> </body>
</html> </html>

View File

@ -24,22 +24,12 @@ describe("GalleryUtils", () => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
it("downloadItem shows dialog in standalone gallery", () => {
const setDialogProps = jest.fn().mockImplementation();
GalleryUtils.downloadItem({ setDialogProps }, undefined, undefined, galleryItem, undefined);
expect(setDialogProps).toBeCalled();
});
it("downloadItem shows dialog in data explorer", () => { it("downloadItem shows dialog in data explorer", () => {
const setDialogProps = jest.fn().mockImplementation();
const container = new ExplorerStub(); const container = new ExplorerStub();
container.showOkCancelModalDialog = jest.fn().mockImplementation(); container.showOkCancelModalDialog = jest.fn().mockImplementation();
GalleryUtils.downloadItem({ setDialogProps }, container, undefined, galleryItem, undefined); GalleryUtils.downloadItem(container, undefined, galleryItem, undefined);
expect(setDialogProps).not.toBeCalled();
expect(container.showOkCancelModalDialog).toBeCalled(); expect(container.showOkCancelModalDialog).toBeCalled();
}); });

View File

@ -1,4 +1,3 @@
import { LinkProps, DialogProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { NotificationConsoleUtils } from "./NotificationConsoleUtils"; import { NotificationConsoleUtils } from "./NotificationConsoleUtils";
@ -10,10 +9,6 @@ import {
GalleryViewerComponent GalleryViewerComponent
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
export interface DialogEnabledComponent {
setDialogProps: (dialogProps: DialogProps) => void;
}
export enum NotebookViewerParams { export enum NotebookViewerParams {
NotebookUrl = "notebookUrl", NotebookUrl = "notebookUrl",
GalleryItemId = "galleryItemId", GalleryItemId = "galleryItemId",
@ -38,48 +33,13 @@ export interface GalleryViewerProps {
searchText: string; searchText: string;
} }
function showOkCancelModalDialog(
component: DialogEnabledComponent,
title: string,
msg: string,
linkProps: LinkProps,
showCloseButton: boolean,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void
): void {
component.setDialogProps({
linkProps,
isModal: true,
visible: true,
title,
subText: msg,
primaryButtonText: okLabel,
secondaryButtonText: cancelLabel,
onPrimaryButtonClick: () => {
component.setDialogProps(undefined);
onOk && onOk();
},
onSecondaryButtonClick: () => {
component.setDialogProps(undefined);
onCancel && onCancel();
},
showCloseButton,
onDismiss: () => component.setDialogProps(undefined)
});
}
export function downloadItem( export function downloadItem(
component: DialogEnabledComponent,
container: ViewModels.Explorer, container: ViewModels.Explorer,
junoClient: JunoClient, junoClient: JunoClient,
data: IGalleryItem, data: IGalleryItem,
onComplete: (item: IGalleryItem) => void onComplete: (item: IGalleryItem) => void
): void { ): void {
const name = data.name; const name = data.name;
if (container) {
container.showOkCancelModalDialog( container.showOkCancelModalDialog(
"Download to My Notebooks", "Download to My Notebooks",
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`, `Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
@ -117,26 +77,6 @@ export function downloadItem(
"Cancel", "Cancel",
undefined undefined
); );
} else {
showOkCancelModalDialog(
component,
"Edit/Run notebook in Cosmos DB data explorer",
`In order to edit/run ${name} in Cosmos DB data explorer, a Cosmos DB account will be needed. If you do not have a Cosmos DB account yet, please create one.`,
{
linkText: "Learn more about Cosmos DB",
linkUrl: "https://azure.microsoft.com/en-us/services/cosmos-db"
},
true,
"Open data explorer",
() => {
window.open("https://cosmos.azure.com");
},
"Create Cosmos DB account",
() => {
window.open("https://ms.portal.azure.com/#create/Microsoft.DocumentDB");
}
);
}
} }
export async function favoriteItem( export async function favoriteItem(

View File

@ -148,7 +148,7 @@ module.exports = function(env = {}, argv = {}) {
chunks: ["notebookViewer"] chunks: ["notebookViewer"]
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: "galleryViewer.html", filename: "gallery/index.html",
template: "src/GalleryViewer/galleryViewer.html", template: "src/GalleryViewer/galleryViewer.html",
chunks: ["galleryViewer"] chunks: ["galleryViewer"]
}), }),