Notebooks Gallery (#59)

* Initial commit

* Address PR comments

* Move notebook related stuff to NotebookManager and dynamically load it

* Add New gallery callout and other UI tweaks

* Update test snapshot
This commit is contained in:
Tanuj Mittal
2020-06-30 11:47:21 -07:00
committed by GitHub
parent dd199e6565
commit 7512b3c1d5
41 changed files with 2801 additions and 1193 deletions

View File

@@ -1,45 +1,151 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { GalleryViewerContainerComponent } from "../Controls/NotebookGallery/GalleryViewerComponent";
import * as ViewModels from "../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../Juno/JunoClient";
import * as GalleryUtils from "../../Utils/GalleryUtils";
import {
GalleryTab as GalleryViewerTab,
GalleryViewerComponent,
GalleryViewerComponentProps,
SortBy
} from "../Controls/NotebookGallery/GalleryViewerComponent";
import {
NotebookViewerComponent,
NotebookViewerComponentProps
} from "../Controls/NotebookViewer/NotebookViewerComponent";
import TabsBase from "./TabsBase";
/**
* 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.Computed<boolean>;
constructor(private getContainer: () => ViewModels.Explorer) {}
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 {
return this.parameters() ? <GalleryViewerContainerComponent container={this.getContainer()} /> : <></>;
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 {
private container: ViewModels.Explorer;
private galleryComponentAdapterProps: GalleryComponentAdapterProps;
private galleryComponentAdapter: GalleryComponentAdapter;
constructor(options: ViewModels.GalleryTabOptions) {
super(options);
this.container = options.container;
this.galleryComponentAdapter = new GalleryComponentAdapter(() => this.getContainer());
this.galleryComponentAdapter.parameters = ko.computed<boolean>(() => {
return this.isTemplateReady() && this.container.isNotebookEnabled();
});
this.container = options.container;
this.galleryComponentAdapterProps = {
container: options.container,
junoClient: options.junoClient,
notebookUrl: options.notebookUrl,
galleryItem: options.galleryItem,
isFavorite: options.isFavorite,
selectedTab: GalleryViewerTab.OfficialSamples,
sortBy: SortBy.MostViewed,
searchText: undefined
};
this.galleryComponentAdapter = new GalleryComponentAdapter(this.galleryComponentAdapterProps);
}
protected getContainer(): ViewModels.Explorer {
return this.container;
}
protected getTabsButtons(): ViewModels.NavbarButtonConfig[] {
return [];
}
protected buildCommandBarOptions(): void {
this.updateNavbarWithTabsButtons();
public updateGalleryParams(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean): void {
this.galleryComponentAdapter.setState({
notebookUrl,
galleryItem,
isFavorite
});
}
}

View File

@@ -45,7 +45,7 @@ export default class NotebookTabV2 extends TabsBase implements ViewModels.Tab {
connectionInfo: this.container.notebookServerInfo(),
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
contentProvider: this.container.notebookContentProvider
contentProvider: this.container.notebookManager?.notebookContentProvider
});
}
@@ -112,6 +112,7 @@ export default class NotebookTabV2 extends TabsBase implements ViewModels.Tab {
const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs();
const saveLabel = "Save";
const publishLabel = "Publish to gallery";
const workspaceLabel = "No Workspace";
const kernelLabel = "No Kernel";
const runLabel = "Run";
@@ -142,7 +143,27 @@ export default class NotebookTabV2 extends TabsBase implements ViewModels.Tab {
commandButtonLabel: saveLabel,
hasPopup: false,
disabled: false,
ariaLabel: saveLabel
ariaLabel: saveLabel,
children: this.container.isGalleryPublishEnabled()
? [
{
iconName: "Save",
onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
commandButtonLabel: saveLabel,
hasPopup: false,
disabled: false,
ariaLabel: saveLabel
},
{
iconName: "PublishContent",
onCommandClick: () => this.publishToGallery(),
commandButtonLabel: publishLabel,
hasPopup: false,
disabled: false,
ariaLabel: publishLabel
}
]
: undefined
},
{
iconSrc: null,
@@ -425,6 +446,11 @@ export default class NotebookTabV2 extends TabsBase implements ViewModels.Tab {
);
}
private publishToGallery = () => {
const notebookContent = this.notebookComponentAdapter.getContent();
this.container.publishNotebook(notebookContent.name, notebookContent.content);
};
private traceTelemetry(actionType: number) {
TelemetryProcessor.trace(actionType, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,

View File

@@ -1,10 +1,12 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import * as DataModels from "../../Contracts/DataModels";
import TabsBase from "./TabsBase";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { NotebookViewerComponent } from "../Controls/NotebookViewer/NotebookViewerComponent";
import * as ViewModels from "../../Contracts/ViewModels";
import {
NotebookViewerComponent,
NotebookViewerComponentProps
} from "../Controls/NotebookViewer/NotebookViewerComponent";
import TabsBase from "./TabsBase";
/**
* Notebook Viewer tab
@@ -12,48 +14,32 @@ import { NotebookViewerComponent } from "../Controls/NotebookViewer/NotebookView
class NotebookViewerComponentAdapter implements ReactAdapter {
// parameters: true: show, false: hide
public parameters: ko.Computed<boolean>;
constructor(
private notebookUrl: string,
private notebookName: string,
private container: ViewModels.Explorer,
private notebookMetadata: DataModels.NotebookMetadata,
private onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
private isLikedNotebook: boolean
) {}
constructor(private notebookUrl: string) {}
public renderComponent(): JSX.Element {
return this.parameters() ? (
<NotebookViewerComponent
notebookUrl={this.notebookUrl}
notebookMetadata={this.notebookMetadata}
notebookName={this.notebookName}
container={this.container}
onNotebookMetadataChange={this.onNotebookMetadataChange}
isLikedNotebook={this.isLikedNotebook}
/>
) : (
<></>
);
const props: NotebookViewerComponentProps = {
notebookUrl: this.notebookUrl,
backNavigationText: undefined,
onBackClick: undefined,
onTagClick: undefined
};
return this.parameters() ? <NotebookViewerComponent {...props} /> : <></>;
}
}
export default class NotebookViewerTab extends TabsBase implements ViewModels.Tab {
private container: ViewModels.Explorer;
public notebookViewerComponentAdapter: NotebookViewerComponentAdapter;
public notebookUrl: string;
public notebookViewerComponentAdapter: NotebookViewerComponentAdapter;
constructor(options: ViewModels.NotebookViewerTabOptions) {
super(options);
this.container = options.container;
this.notebookUrl = options.notebookUrl;
this.notebookViewerComponentAdapter = new NotebookViewerComponentAdapter(
options.notebookUrl,
options.notebookName,
options.container,
options.notebookMetadata,
options.onNotebookMetadataChange,
options.isLikedNotebook
);
this.notebookViewerComponentAdapter = new NotebookViewerComponentAdapter(options.notebookUrl);
this.notebookViewerComponentAdapter.parameters = ko.computed<boolean>(() => {
if (this.isTemplateReady() && this.container.isNotebookEnabled()) {