/** * Gallery Viewer */ import * as React from "react"; import * as DataModels from "../../../Contracts/DataModels"; import * as ViewModels from "../../../Contracts/ViewModels"; import { GalleryCardComponent } from "./Cards/GalleryCardComponent"; import { Stack, IStackTokens } from "office-ui-fabric-react"; import { JunoUtils } from "../../../Utils/JunoUtils"; import { CosmosClient } from "../../../Common/CosmosClient"; import { config } from "../../../Config"; import path from "path"; import { SessionStorageUtility, StorageKey } from "../../../Shared/StorageUtility"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import * as TabComponent from "../Tabs/TabComponent"; import "./GalleryViewerComponent.less"; export interface GalleryCardsComponentProps { data: DataModels.GitHubInfoJunoResponse[]; userMetadata: DataModels.UserMetadata; onNotebookMetadataChange: ( officialSamplesIndex: number, notebookMetadata: DataModels.NotebookMetadata ) => Promise; onClick: ( url: string, notebookMetadata: DataModels.NotebookMetadata, onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise, isLikedNotebook: boolean ) => Promise; } export class GalleryCardsComponent extends React.Component { private sectionStackTokens: IStackTokens = { childrenGap: 30 }; public render(): JSX.Element { return ( {this.props.data.map((githubInfo: DataModels.GitHubInfoJunoResponse) => { const name = githubInfo.name; const url = githubInfo.downloadUrl; const notebookMetadata = githubInfo.metadata || { date: "2008-12-01", description: "Great notebook", tags: ["favorite", "sample"], author: "Laurent Nguyen", views: 432, likes: 123, downloads: 56, imageUrl: "https://media.magazine.ferrari.com/images/2019/02/27/170304506-c1bcf028-b513-45f6-9f27-0cadac619c3d.jpg" }; const officialSamplesIndex = githubInfo.officialSamplesIndex; const isLikedNotebook = githubInfo.isLikedNotebook; const updateTabsStatePerNotebook = this.props.onNotebookMetadataChange ? (notebookMetadata: DataModels.NotebookMetadata): Promise => this.props.onNotebookMetadataChange(officialSamplesIndex, notebookMetadata) : undefined; return ( name !== ".gitignore" && url && ( => this.props.onClick(url, notebookMetadata, updateTabsStatePerNotebook, isLikedNotebook) } /> ) ); })} ); } } export interface FullWidthTabsProps { officialSamplesContent: DataModels.GitHubInfoJunoResponse[]; likedNotebooksContent: DataModels.GitHubInfoJunoResponse[]; userMetadata: DataModels.UserMetadata; onClick: ( url: string, notebookMetadata: DataModels.NotebookMetadata, onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise, isLikedNotebook: boolean ) => Promise; } interface FullWidthTabsState { activeTabIndex: number; officialSamplesContent: DataModels.GitHubInfoJunoResponse[]; likedNotebooksContent: DataModels.GitHubInfoJunoResponse[]; userMetadata: DataModels.UserMetadata; } export class FullWidthTabs extends React.Component { private authorizationToken = CosmosClient.authorizationToken(); private appTabs: TabComponent.Tab[]; constructor(props: FullWidthTabsProps) { super(props); this.state = { activeTabIndex: 0, officialSamplesContent: this.props.officialSamplesContent, likedNotebooksContent: this.props.likedNotebooksContent, userMetadata: this.props.userMetadata }; this.appTabs = [ { title: "Official Samples", content: { className: "", render: (): JSX.Element => ( ) }, isVisible: (): boolean => true }, { title: "Liked Notebooks", content: { className: "", render: (): JSX.Element => ( ) }, isVisible: (): boolean => true } ]; } public updateTabsState = async ( officialSamplesIndex: number, notebookMetadata: DataModels.NotebookMetadata ): Promise => { let currentLikedNotebooksContent = [...this.state.likedNotebooksContent]; let currentUserMetadata = { ...this.state.userMetadata }; let currentLikedNotebooks = [...currentUserMetadata.likedNotebooks]; const currentOfficialSamplesContent = [...this.state.officialSamplesContent]; const currentOfficialSamplesObject = { ...currentOfficialSamplesContent[officialSamplesIndex] }; const metadata = { ...currentOfficialSamplesObject.metadata }; const metadataLikesUpdates = metadata.likes - notebookMetadata.likes; metadata.views = notebookMetadata.views; metadata.downloads = notebookMetadata.downloads; metadata.likes = notebookMetadata.likes; currentOfficialSamplesObject.metadata = metadata; // Notebook has been liked. Add To likedNotebooksContent, update isLikedNotebook flag if (metadataLikesUpdates < 0) { currentOfficialSamplesObject.isLikedNotebook = true; currentLikedNotebooksContent = currentLikedNotebooksContent.concat(currentOfficialSamplesObject); currentLikedNotebooks = currentLikedNotebooks.concat(currentOfficialSamplesObject.path); currentUserMetadata = { likedNotebooks: currentLikedNotebooks }; } else if (metadataLikesUpdates > 0) { // Notebook has been unliked. Remove from likedNotebooksContent after matching the path, update isLikedNotebook flag currentOfficialSamplesObject.isLikedNotebook = false; const likedNotebookIndex = currentLikedNotebooks.findIndex((path: string) => { return path === currentOfficialSamplesObject.path; }); currentLikedNotebooksContent.splice(likedNotebookIndex, 1); currentLikedNotebooks.splice(likedNotebookIndex, 1); currentUserMetadata = { likedNotebooks: currentLikedNotebooks }; } currentOfficialSamplesContent[officialSamplesIndex] = currentOfficialSamplesObject; this.setState({ activeTabIndex: 0, userMetadata: currentUserMetadata, likedNotebooksContent: currentLikedNotebooksContent, officialSamplesContent: currentOfficialSamplesContent }); JunoUtils.updateNotebookMetadata(this.authorizationToken, notebookMetadata).then( async () => { if (metadataLikesUpdates !== 0) { JunoUtils.updateUserMetadata(this.authorizationToken, currentUserMetadata); // TODO: update state here? } }, error => { NotificationConsoleUtils.logConsoleMessage( ConsoleDataType.Error, `Error updating notebook metadata: ${JSON.stringify(error)}` ); // TODO add telemetry } ); }; private onTabIndexChange = (activeTabIndex: number): void => this.setState({ activeTabIndex }); public render(): JSX.Element { return ( ); } } export interface GalleryViewerContainerComponentProps { container: ViewModels.Explorer; } interface GalleryViewerContainerComponentState { officialSamplesData: DataModels.GitHubInfoJunoResponse[]; likedNotebooksData: DataModels.LikedNotebooksJunoResponse; } export class GalleryViewerContainerComponent extends React.Component< GalleryViewerContainerComponentProps, GalleryViewerContainerComponentState > { constructor(props: GalleryViewerContainerComponentProps) { super(props); this.state = { officialSamplesData: undefined, likedNotebooksData: undefined }; } componentDidMount(): void { const authToken = CosmosClient.authorizationToken(); JunoUtils.getOfficialSampleNotebooks(authToken).then( (data1: DataModels.GitHubInfoJunoResponse[]) => { const officialSamplesData = data1; JunoUtils.getLikedNotebooks(authToken).then( (data2: DataModels.LikedNotebooksJunoResponse) => { const likedNotebooksData = data2; officialSamplesData.map((value: DataModels.GitHubInfoJunoResponse, index: number) => { value.officialSamplesIndex = index; value.isLikedNotebook = likedNotebooksData.userMetadata.likedNotebooks.includes(value.path); }); likedNotebooksData.likedNotebooksContent.map((value: DataModels.GitHubInfoJunoResponse) => { value.isLikedNotebook = true; value.officialSamplesIndex = officialSamplesData.findIndex( (officialSample: DataModels.GitHubInfoJunoResponse) => { return officialSample.path === value.path; } ); }); this.setState({ officialSamplesData: officialSamplesData, likedNotebooksData: likedNotebooksData }); }, error => { NotificationConsoleUtils.logConsoleMessage( ConsoleDataType.Error, `Error fetching liked notebooks: ${JSON.stringify(error)}` ); // TODO Add telemetry } ); }, error => { NotificationConsoleUtils.logConsoleMessage( ConsoleDataType.Error, `Error fetching sample notebooks: ${JSON.stringify(error)}` ); // TODO Add telemetry } ); } public render(): JSX.Element { return this.state.officialSamplesData && this.state.likedNotebooksData ? ( ) : ( <> ); } } export interface GalleryViewerComponentProps { container: ViewModels.Explorer; officialSamplesData: DataModels.GitHubInfoJunoResponse[]; likedNotebookData: DataModels.LikedNotebooksJunoResponse; } export class GalleryViewerComponent extends React.Component { public render(): JSX.Element { return this.props.container ? (
) : (
); } public getOfficialSamplesData(): DataModels.GitHubInfoJunoResponse[] { return this.props.officialSamplesData; } public getLikedNotebookData(): DataModels.LikedNotebooksJunoResponse { return this.props.likedNotebookData; } public openNotebookViewer = async ( url: string, notebookMetadata: DataModels.NotebookMetadata, onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise, isLikedNotebook: boolean ): Promise => { if (!this.props.container) { SessionStorageUtility.setEntryString( StorageKey.NotebookMetadata, notebookMetadata ? JSON.stringify(notebookMetadata) : null ); SessionStorageUtility.setEntryString(StorageKey.NotebookName, path.basename(url)); window.open(`${config.hostedExplorerURL}notebookViewer.html?notebookurl=${url}`, "_blank"); } else { this.props.container.openNotebookViewer(url, notebookMetadata, onNotebookMetadataChange, isLikedNotebook); } }; }