mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
Migrate Publish Notebook Pane to React (#641)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
@@ -1,25 +1,25 @@
|
||||
import { Card } from "@uifabric/react-cards";
|
||||
import {
|
||||
BaseButton,
|
||||
Button,
|
||||
FontWeights,
|
||||
Icon,
|
||||
IconButton,
|
||||
Image,
|
||||
ImageFit,
|
||||
Persona,
|
||||
Text,
|
||||
Link,
|
||||
BaseButton,
|
||||
Button,
|
||||
LinkBase,
|
||||
Persona,
|
||||
Separator,
|
||||
TooltipHost,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
Text,
|
||||
TooltipHost,
|
||||
} from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
|
||||
import { IGalleryItem } from "../../../../Juno/JunoClient";
|
||||
import * as FileSystemUtil from "../../../Notebook/FileSystemUtil";
|
||||
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
|
||||
|
||||
export interface GalleryCardComponentProps {
|
||||
data: IGalleryItem;
|
||||
@@ -34,166 +34,48 @@ export interface GalleryCardComponentProps {
|
||||
onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void;
|
||||
}
|
||||
|
||||
interface GalleryCardComponentState {
|
||||
isDeletingPublishedNotebook: boolean;
|
||||
}
|
||||
export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps> = ({
|
||||
data,
|
||||
isFavorite,
|
||||
showDownload,
|
||||
showDelete,
|
||||
onClick,
|
||||
onTagClick,
|
||||
onFavoriteClick,
|
||||
onUnfavoriteClick,
|
||||
onDownloadClick,
|
||||
onDeleteClick,
|
||||
}: GalleryCardComponentProps) => {
|
||||
const CARD_WIDTH = 256;
|
||||
const cardImageHeight = 144;
|
||||
const cardDescriptionMaxChars = 80;
|
||||
const cardItemGapBig = 10;
|
||||
const cardItemGapSmall = 8;
|
||||
const cardDeleteSpinnerHeight = 360;
|
||||
const smallTextLineHeight = 18;
|
||||
|
||||
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps, GalleryCardComponentState> {
|
||||
public static readonly CARD_WIDTH = 256;
|
||||
private static readonly cardImageHeight = 144;
|
||||
public static readonly cardHeightToWidthRatio =
|
||||
GalleryCardComponent.cardImageHeight / GalleryCardComponent.CARD_WIDTH;
|
||||
private static readonly cardDescriptionMaxChars = 80;
|
||||
private static readonly cardItemGapBig = 10;
|
||||
private static readonly cardItemGapSmall = 8;
|
||||
private static readonly cardDeleteSpinnerHeight = 360;
|
||||
private static readonly smallTextLineHeight = 18;
|
||||
const [isDeletingPublishedNotebook, setIsDeletingPublishedNotebook] = useState<boolean>(false);
|
||||
|
||||
constructor(props: GalleryCardComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isDeletingPublishedNotebook: false,
|
||||
};
|
||||
}
|
||||
const cardButtonsVisible = isFavorite !== undefined || showDownload || showDelete;
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
};
|
||||
const dateString = new Date(data.created).toLocaleString("default", options);
|
||||
const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
|
||||
|
||||
public render(): JSX.Element {
|
||||
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
};
|
||||
const dateString = new Date(this.props.data.created).toLocaleString("default", options);
|
||||
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
|
||||
|
||||
return (
|
||||
<Card
|
||||
style={{ background: "white" }}
|
||||
aria-label={cardTitle}
|
||||
data-is-focusable="true"
|
||||
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
|
||||
onClick={(event) => this.onClick(event, this.props.onClick)}
|
||||
>
|
||||
{this.state.isDeletingPublishedNotebook && (
|
||||
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
|
||||
<Spinner
|
||||
size={SpinnerSize.large}
|
||||
label={`Deleting '${cardTitle}'`}
|
||||
styles={{ root: { height: GalleryCardComponent.cardDeleteSpinnerHeight } }}
|
||||
/>
|
||||
</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>
|
||||
<Image
|
||||
src={this.props.data.thumbnailUrl}
|
||||
width={GalleryCardComponent.CARD_WIDTH}
|
||||
height={GalleryCardComponent.cardImageHeight}
|
||||
imageFit={ImageFit.cover}
|
||||
alt={`${cardTitle} cover image`}
|
||||
/>
|
||||
</Card.Item>
|
||||
|
||||
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
|
||||
<Text variant="small" nowrap styles={{ root: { height: GalleryCardComponent.smallTextLineHeight } }}>
|
||||
{this.props.data.tags ? (
|
||||
this.props.data.tags.map((tag, index, array) => (
|
||||
<span key={tag}>
|
||||
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
|
||||
{index === array.length - 1 ? <></> : ", "}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<br />
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
styles={{
|
||||
root: {
|
||||
fontWeight: FontWeights.semibold,
|
||||
paddingTop: GalleryCardComponent.cardItemGapSmall,
|
||||
paddingBottom: GalleryCardComponent.cardItemGapSmall,
|
||||
},
|
||||
}}
|
||||
nowrap
|
||||
>
|
||||
{cardTitle}
|
||||
</Text>
|
||||
|
||||
<Text variant="small" styles={{ root: { height: GalleryCardComponent.smallTextLineHeight * 2 } }}>
|
||||
{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
|
||||
)}
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
private renderTruncatedDescription = (): string => {
|
||||
let truncatedDescription = this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars);
|
||||
if (this.props.data.description.length > GalleryCardComponent.cardDescriptionMaxChars) {
|
||||
const renderTruncatedDescription = (): string => {
|
||||
let truncatedDescription = data.description.substr(0, cardDescriptionMaxChars);
|
||||
if (data.description.length > cardDescriptionMaxChars) {
|
||||
truncatedDescription = `${truncatedDescription} ...`;
|
||||
}
|
||||
return truncatedDescription;
|
||||
};
|
||||
|
||||
private generateIconText = (iconName: string, text: string): JSX.Element => {
|
||||
const generateIconText = (iconName: string, text: string): JSX.Element => {
|
||||
return (
|
||||
<Text variant="tiny" styles={{ root: { color: "#605E5C", paddingRight: GalleryCardComponent.cardItemGapSmall } }}>
|
||||
<Text variant="tiny" styles={{ root: { color: "#605E5C", paddingRight: cardItemGapSmall } }}>
|
||||
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
|
||||
</Text>
|
||||
);
|
||||
@@ -203,7 +85,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||
* Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is
|
||||
* to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button)
|
||||
*/
|
||||
private generateIconButtonWithTooltip = (
|
||||
const generateIconButtonWithTooltip = (
|
||||
iconName: string,
|
||||
title: string,
|
||||
horizontalAlign: "right" | "left",
|
||||
@@ -220,13 +102,13 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||
iconProps={{ iconName }}
|
||||
title={title}
|
||||
ariaLabel={title}
|
||||
onClick={(event) => this.onClick(event, activate)}
|
||||
onClick={(event) => handlerOnClick(event, activate)}
|
||||
/>
|
||||
</TooltipHost>
|
||||
);
|
||||
};
|
||||
|
||||
private onClick = (
|
||||
const handlerOnClick = (
|
||||
event:
|
||||
| React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>
|
||||
| React.MouseEvent<
|
||||
@@ -239,4 +121,112 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||
event.preventDefault();
|
||||
activate();
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
style={{ background: "white" }}
|
||||
aria-label={cardTitle}
|
||||
data-is-focusable="true"
|
||||
tokens={{ width: CARD_WIDTH, childrenGap: 0 }}
|
||||
onClick={(event) => handlerOnClick(event, onClick)}
|
||||
>
|
||||
{isDeletingPublishedNotebook && (
|
||||
<Card.Item tokens={{ padding: cardItemGapBig }}>
|
||||
<Spinner
|
||||
size={SpinnerSize.large}
|
||||
label={`Deleting '${cardTitle}'`}
|
||||
styles={{ root: { height: cardDeleteSpinnerHeight } }}
|
||||
/>
|
||||
</Card.Item>
|
||||
)}
|
||||
{!isDeletingPublishedNotebook && (
|
||||
<>
|
||||
<Card.Item tokens={{ padding: cardItemGapBig }}>
|
||||
<Persona imageUrl={data.isSample && CosmosDBLogo} text={data.author} secondaryText={dateString} />
|
||||
</Card.Item>
|
||||
|
||||
<Card.Item>
|
||||
<Image
|
||||
src={data.thumbnailUrl}
|
||||
width={CARD_WIDTH}
|
||||
height={cardImageHeight}
|
||||
imageFit={ImageFit.cover}
|
||||
alt={`${cardTitle} cover image`}
|
||||
/>
|
||||
</Card.Item>
|
||||
|
||||
<Card.Section styles={{ root: { padding: cardItemGapBig } }}>
|
||||
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight } }}>
|
||||
{data.tags ? (
|
||||
data.tags.map((tag, index, array) => (
|
||||
<span key={tag}>
|
||||
<Link onClick={(event) => handlerOnClick(event, () => onTagClick(tag))}>{tag}</Link>
|
||||
{index === array.length - 1 ? <></> : ", "}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<br />
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
styles={{
|
||||
root: {
|
||||
fontWeight: FontWeights.semibold,
|
||||
paddingTop: cardItemGapSmall,
|
||||
paddingBottom: cardItemGapSmall,
|
||||
},
|
||||
}}
|
||||
nowrap
|
||||
>
|
||||
{cardTitle}
|
||||
</Text>
|
||||
|
||||
<Text variant="small" styles={{ root: { height: smallTextLineHeight * 2 } }}>
|
||||
{renderTruncatedDescription()}
|
||||
</Text>
|
||||
|
||||
<span>
|
||||
{data.views !== undefined && generateIconText("RedEye", data.views.toString())}
|
||||
{data.downloads !== undefined && generateIconText("Download", data.downloads.toString())}
|
||||
{data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())}
|
||||
</span>
|
||||
</Card.Section>
|
||||
|
||||
{cardButtonsVisible && (
|
||||
<Card.Section
|
||||
styles={{
|
||||
root: {
|
||||
marginLeft: cardItemGapBig,
|
||||
marginRight: cardItemGapBig,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Separator styles={{ root: { padding: 0, height: 1 } }} />
|
||||
|
||||
<span>
|
||||
{isFavorite !== undefined &&
|
||||
generateIconButtonWithTooltip(
|
||||
isFavorite ? "HeartFill" : "Heart",
|
||||
isFavorite ? "Unfavorite" : "Favorite",
|
||||
"left",
|
||||
isFavorite ? onUnfavoriteClick : onFavoriteClick
|
||||
)}
|
||||
|
||||
{showDownload && generateIconButtonWithTooltip("Download", "Download", "left", onDownloadClick)}
|
||||
|
||||
{showDelete &&
|
||||
generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
|
||||
onDeleteClick(
|
||||
() => setIsDeletingPublishedNotebook(true),
|
||||
() => setIsDeletingPublishedNotebook(false)
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</Card.Section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
||||
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
export interface CodeOfConductComponentProps {
|
||||
junoClient: JunoClient;
|
||||
onAcceptCodeOfConduct: (result: boolean) => void;
|
||||
}
|
||||
|
||||
interface CodeOfConductComponentState {
|
||||
readCodeOfConduct: boolean;
|
||||
}
|
||||
|
||||
export class CodeOfConductComponent extends React.Component<CodeOfConductComponentProps, CodeOfConductComponentState> {
|
||||
private viewCodeOfConductTraced: boolean;
|
||||
private descriptionPara1: string;
|
||||
private descriptionPara2: string;
|
||||
private descriptionPara3: string;
|
||||
private link1: { label: string; url: string };
|
||||
|
||||
constructor(props: CodeOfConductComponentProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
readCodeOfConduct: false,
|
||||
};
|
||||
|
||||
this.descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
|
||||
this.descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
|
||||
this.descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
|
||||
this.link1 = { label: "code of conduct.", url: CodeOfConductEndpoints.codeOfConduct };
|
||||
}
|
||||
|
||||
private async acceptCodeOfConduct(): Promise<void> {
|
||||
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
|
||||
|
||||
try {
|
||||
const response = await this.props.junoClient.acceptCodeOfConduct();
|
||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
||||
}
|
||||
|
||||
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
|
||||
|
||||
this.props.onAcceptCodeOfConduct(response.data);
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.NotebooksGalleryAcceptCodeOfConduct,
|
||||
{
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||
}
|
||||
}
|
||||
|
||||
private onChangeCheckbox = (): void => {
|
||||
this.setState({ readCodeOfConduct: !this.state.readCodeOfConduct });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
if (!this.viewCodeOfConductTraced) {
|
||||
this.viewCodeOfConductTraced = true;
|
||||
trace(Action.NotebooksGalleryViewCodeOfConduct);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }}>
|
||||
<Stack.Item>
|
||||
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{this.descriptionPara1}</Text>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<Text>{this.descriptionPara2}</Text>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<Text>
|
||||
{this.descriptionPara3}
|
||||
<Link href={this.link1.url} target="_blank">
|
||||
{this.link1.label}
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: {
|
||||
margin: 0,
|
||||
padding: "2 0 2 0",
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
},
|
||||
}}
|
||||
label="I have read and accept the code of conduct."
|
||||
onChange={this.onChangeCheckbox}
|
||||
/>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<PrimaryButton
|
||||
ariaLabel="Continue"
|
||||
title="Continue"
|
||||
onClick={async () => await this.acceptCodeOfConduct()}
|
||||
tabIndex={0}
|
||||
className="genericPaneSubmitBtn"
|
||||
text="Continue"
|
||||
disabled={!this.state.readCodeOfConduct}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
jest.mock("../../../Juno/JunoClient");
|
||||
jest.mock("../../../../Juno/JunoClient");
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { CodeOfConductComponent, CodeOfConductComponentProps } from "./CodeOfConductComponent";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||
import { CodeOfConductComponent, CodeOfConductComponentProps } from ".";
|
||||
import { HttpStatusCodes } from "../../../../Common/Constants";
|
||||
import { JunoClient } from "../../../../Juno/JunoClient";
|
||||
|
||||
describe("CodeOfConductComponent", () => {
|
||||
let codeOfConductProps: CodeOfConductComponentProps;
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Checkbox, Link, PrimaryButton, Stack, Text } from "office-ui-fabric-react";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import { CodeOfConductEndpoints, HttpStatusCodes } from "../../../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../../../../Common/ErrorHandlingUtils";
|
||||
import { JunoClient } from "../../../../Juno/JunoClient";
|
||||
import { Action } from "../../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { trace, traceFailure, traceStart, traceSuccess } from "../../../../Shared/Telemetry/TelemetryProcessor";
|
||||
|
||||
export interface CodeOfConductComponentProps {
|
||||
junoClient: JunoClient;
|
||||
onAcceptCodeOfConduct: (result: boolean) => void;
|
||||
}
|
||||
|
||||
export const CodeOfConductComponent: FunctionComponent<CodeOfConductComponentProps> = ({
|
||||
junoClient,
|
||||
onAcceptCodeOfConduct,
|
||||
}: CodeOfConductComponentProps) => {
|
||||
const descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
|
||||
const descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
|
||||
const descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
|
||||
const link1: { label: string; url: string } = {
|
||||
label: "code of conduct.",
|
||||
url: CodeOfConductEndpoints.codeOfConduct,
|
||||
};
|
||||
|
||||
const [readCodeOfConduct, setReadCodeOfConduct] = useState<boolean>(false);
|
||||
|
||||
const acceptCodeOfConduct = async (): Promise<void> => {
|
||||
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
|
||||
|
||||
try {
|
||||
const response = await junoClient.acceptCodeOfConduct();
|
||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
||||
}
|
||||
|
||||
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
|
||||
|
||||
onAcceptCodeOfConduct(response.data);
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.NotebooksGalleryAcceptCodeOfConduct,
|
||||
{
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeCheckbox = (): void => {
|
||||
setReadCodeOfConduct(!readCodeOfConduct);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
trace(Action.NotebooksGalleryViewCodeOfConduct);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }}>
|
||||
<Stack.Item>
|
||||
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{descriptionPara1}</Text>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<Text>{descriptionPara2}</Text>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<Text>
|
||||
{descriptionPara3}
|
||||
<Link href={link1.url} target="_blank">
|
||||
{link1.label}
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: {
|
||||
margin: 0,
|
||||
padding: "2 0 2 0",
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
},
|
||||
}}
|
||||
label="I have read and accept the code of conduct."
|
||||
onChange={onChangeCheckbox}
|
||||
/>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<PrimaryButton
|
||||
ariaLabel="Continue"
|
||||
title="Continue"
|
||||
onClick={async () => await acceptCodeOfConduct()}
|
||||
tabIndex={0}
|
||||
className="genericPaneSubmitBtn"
|
||||
text="Continue"
|
||||
disabled={!readCodeOfConduct}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -34,6 +34,7 @@ import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||
import "./GalleryViewerComponent.less";
|
||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||
|
||||
const CARD_WIDTH = 256;
|
||||
export interface GalleryViewerComponentProps {
|
||||
container?: Explorer;
|
||||
junoClient: JunoClient;
|
||||
@@ -643,7 +644,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
|
||||
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
|
||||
if (itemIndex === 0) {
|
||||
this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH) || this.columnCount;
|
||||
this.columnCount = Math.floor(visibleRect.width / CARD_WIDTH) || this.columnCount;
|
||||
this.rowCount = GalleryViewerComponent.rowsPerPage;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user