mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
Added support for taking screenshot during Notebook publish to Gallery (#108)
* Added support for taking screenshot - Screenshot is taken using html2canvas package - Converted to base 64 and uploaded to metadata - For Using first display output - Notebok object is passed instead of string, to publish pane - The first cell with output present is parsed out - The dom is also parsed to get corresponding div element to take screenshot of the first output * fixed bug * Addressed PR comments - FIxed bug that didn't capture screenshot when mutiple notebook tabs are opened * removed unnecessary dependencies * fixed compile issues * more edits
This commit is contained in:
committed by
GitHub
parent
acc65c9588
commit
dc67c5f40b
@@ -8,6 +8,7 @@ import { actions, createContentRef, createKernelRef, selectors } from "@nteract/
|
||||
import VirtualCommandBarComponent from "./VirtualCommandBarComponent";
|
||||
import { NotebookContentItem } from "../NotebookContentItem";
|
||||
import { NotebookComponentBootstrapper } from "./NotebookComponentBootstrapper";
|
||||
import { CdbAppState } from "./types";
|
||||
|
||||
export interface NotebookComponentAdapterOptions {
|
||||
contentItem: NotebookContentItem;
|
||||
@@ -18,6 +19,7 @@ export interface NotebookComponentAdapterOptions {
|
||||
|
||||
export class NotebookComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
||||
private onUpdateKernelInfo: () => void;
|
||||
public getNotebookParentElement: () => HTMLElement;
|
||||
public parameters: any;
|
||||
|
||||
constructor(options: NotebookComponentAdapterOptions) {
|
||||
@@ -44,6 +46,11 @@ export class NotebookComponentAdapter extends NotebookComponentBootstrapper impl
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.getNotebookParentElement = () => {
|
||||
const cdbAppState = this.getStore().getState() as CdbAppState;
|
||||
return cdbAppState.cdb.currentNotebookParentElements.get(this.contentRef);
|
||||
};
|
||||
}
|
||||
|
||||
protected renderExtraComponent = (): JSX.Element => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@nteract/core";
|
||||
import * as Immutable from "immutable";
|
||||
import { Provider } from "react-redux";
|
||||
import { CellType, CellId, toJS } from "@nteract/commutable";
|
||||
import { CellType, CellId, ImmutableNotebook } from "@nteract/commutable";
|
||||
import { Store, AnyAction } from "redux";
|
||||
|
||||
import "./NotebookComponent.less";
|
||||
@@ -71,14 +71,14 @@ export class NotebookComponentBootstrapper {
|
||||
);
|
||||
}
|
||||
|
||||
public getContent(): { name: string; content: string } {
|
||||
public getContent(): { name: string; content: string | ImmutableNotebook } {
|
||||
const record = this.getStore()
|
||||
.getState()
|
||||
.core.entities.contents.byRef.get(this.contentRef);
|
||||
let content: string;
|
||||
let content: string | ImmutableNotebook;
|
||||
switch (record.model.type) {
|
||||
case "notebook":
|
||||
content = JSON.stringify(toJS(record.model.notebook));
|
||||
content = record.model.notebook;
|
||||
break;
|
||||
case "file":
|
||||
content = record.model.text;
|
||||
|
||||
@@ -84,3 +84,22 @@ export const traceNotebookTelemetry = (payload: {
|
||||
payload
|
||||
};
|
||||
};
|
||||
|
||||
export const UPDATE_NOTEBOOK_PARENT_DOM_ELTS = "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
|
||||
export interface UpdateNotebookParentDomEltAction {
|
||||
type: "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
|
||||
payload: {
|
||||
contentRef: ContentRef;
|
||||
parentElt: HTMLElement;
|
||||
};
|
||||
}
|
||||
|
||||
export const UpdateNotebookParentDomElt = (payload: {
|
||||
contentRef: ContentRef;
|
||||
parentElt: HTMLElement;
|
||||
}): UpdateNotebookParentDomEltAction => {
|
||||
return {
|
||||
type: UPDATE_NOTEBOOK_PARENT_DOM_ELTS,
|
||||
payload
|
||||
};
|
||||
};
|
||||
|
||||
@@ -82,6 +82,19 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: {
|
||||
const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction;
|
||||
var parentEltsMap = state.get("currentNotebookParentElements");
|
||||
const contentRef = typedAction.payload.contentRef;
|
||||
const parentElt = typedAction.payload.parentElt;
|
||||
if (parentElt) {
|
||||
parentEltsMap.set(contentRef, parentElt);
|
||||
} else {
|
||||
parentEltsMap.delete(contentRef);
|
||||
}
|
||||
return state.set("currentNotebookParentElements", parentEltsMap);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as Immutable from "immutable";
|
||||
import { AppState } from "@nteract/core";
|
||||
import { AppState, ContentRef } from "@nteract/core";
|
||||
|
||||
import { Notebook } from "../../../Common/Constants";
|
||||
import { CellId } from "@nteract/commutable";
|
||||
@@ -9,6 +9,7 @@ export interface CdbRecordProps {
|
||||
defaultExperience: string | undefined;
|
||||
kernelRestartDelayMs: number;
|
||||
hoveredCellId: CellId | undefined;
|
||||
currentNotebookParentElements: Map<ContentRef, HTMLElement>;
|
||||
}
|
||||
|
||||
export type CdbRecord = Immutable.RecordOf<CdbRecordProps>;
|
||||
@@ -21,5 +22,6 @@ export const makeCdbRecord = Immutable.Record<CdbRecordProps>({
|
||||
databaseAccountName: undefined,
|
||||
defaultExperience: undefined,
|
||||
kernelRestartDelayMs: Notebook.kernelRestartInitialDelayMs,
|
||||
hoveredCellId: undefined
|
||||
hoveredCellId: undefined,
|
||||
currentNotebookParentElements: new Map<ContentRef, HTMLElement>()
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import { DialogProps } from "../Controls/DialogReactComponent/DialogComponent";
|
||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||
import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter";
|
||||
import { getFullName } from "../../Utils/UserUtils";
|
||||
import { ImmutableNotebook } from "@nteract/commutable";
|
||||
import Explorer from "../Explorer";
|
||||
|
||||
export interface NotebookManagerOptions {
|
||||
@@ -108,8 +109,12 @@ export default class NotebookManager {
|
||||
this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope);
|
||||
}
|
||||
|
||||
public openPublishNotebookPane(name: string, content: string): void {
|
||||
this.publishNotebookPaneAdapter.open(name, getFullName(), content);
|
||||
public openPublishNotebookPane(
|
||||
name: string,
|
||||
content: string | ImmutableNotebook,
|
||||
parentDomElement: HTMLElement
|
||||
): void {
|
||||
this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement);
|
||||
}
|
||||
|
||||
// Octokit's error handler uses any
|
||||
|
||||
@@ -30,11 +30,18 @@ import { CellType } from "@nteract/commutable/src";
|
||||
import "./NotebookRenderer.less";
|
||||
import HoverableCell from "./decorators/HoverableCell";
|
||||
import CellLabeler from "./decorators/CellLabeler";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
|
||||
export interface NotebookRendererProps {
|
||||
export interface NotebookRendererBaseProps {
|
||||
contentRef: any;
|
||||
}
|
||||
|
||||
interface NotebookRendererDispatchProps {
|
||||
updateNotebookParentDomElt: (contentRef: ContentRef, parentElt: HTMLElement) => void;
|
||||
}
|
||||
|
||||
type NotebookRendererProps = NotebookRendererBaseProps & NotebookRendererDispatchProps;
|
||||
|
||||
interface PassedEditorProps {
|
||||
id: string;
|
||||
contentRef: ContentRef;
|
||||
@@ -68,6 +75,8 @@ const decorate = (id: string, contentRef: ContentRef, cell_type: CellType, child
|
||||
};
|
||||
|
||||
class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||
private notebookRendererRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: NotebookRendererProps) {
|
||||
super(props);
|
||||
|
||||
@@ -78,13 +87,22 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||
|
||||
componentDidMount() {
|
||||
loadTransform(this.props as any);
|
||||
this.props.updateNotebookParentDomElt(this.props.contentRef, this.notebookRendererRef.current);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.props.updateNotebookParentDomElt(this.props.contentRef, this.notebookRendererRef.current);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.updateNotebookParentDomElt(this.props.contentRef, undefined);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<div className="NotebookRendererContainer">
|
||||
<div className="NotebookRenderer">
|
||||
<div className="NotebookRenderer" ref={this.notebookRendererRef}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<KeyboardShortcuts contentRef={this.props.contentRef}>
|
||||
<Cells contentRef={this.props.contentRef}>
|
||||
@@ -146,7 +164,7 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||
}
|
||||
}
|
||||
|
||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
|
||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererBaseProps) => {
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||
return {
|
||||
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => {
|
||||
@@ -156,6 +174,14 @@ const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: Noteboo
|
||||
component: transform
|
||||
})
|
||||
);
|
||||
},
|
||||
updateNotebookParentDomElt: (contentRef: ContentRef, parentElt: HTMLElement) => {
|
||||
return dispatch(
|
||||
cdbActions.UpdateNotebookParentDomElt({
|
||||
contentRef,
|
||||
parentElt
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import {
|
||||
ImmutableNotebook,
|
||||
MediaBundle,
|
||||
CodeCellParams,
|
||||
MarkdownCellParams,
|
||||
makeCodeCell,
|
||||
makeMarkdownCell,
|
||||
makeNotebookRecord
|
||||
} from "@nteract/commutable";
|
||||
import { List, Map } from "immutable";
|
||||
|
||||
const fileName = "file";
|
||||
const notebookName = "file.ipynb";
|
||||
@@ -7,6 +17,57 @@ const filePath = `folder/${fileName}`;
|
||||
const notebookPath = `folder/${notebookName}`;
|
||||
const gitHubFileUri = GitHubUtils.toContentUri("owner", "repo", "branch", filePath);
|
||||
const gitHubNotebookUri = GitHubUtils.toContentUri("owner", "repo", "branch", notebookPath);
|
||||
const notebookRecord = makeNotebookRecord({
|
||||
cellOrder: List.of("0", "1", "2", "3"),
|
||||
cellMap: Map({
|
||||
"0": makeMarkdownCell({
|
||||
cell_type: "markdown",
|
||||
source: "abc",
|
||||
metadata: undefined
|
||||
} as MarkdownCellParams),
|
||||
"1": makeCodeCell({
|
||||
cell_type: "code",
|
||||
execution_count: undefined,
|
||||
metadata: undefined,
|
||||
source: "print(5)",
|
||||
outputs: List.of({
|
||||
name: "stdout",
|
||||
output_type: "stream",
|
||||
text: "5"
|
||||
})
|
||||
} as CodeCellParams),
|
||||
"2": makeCodeCell({
|
||||
cell_type: "code",
|
||||
execution_count: undefined,
|
||||
metadata: undefined,
|
||||
source: 'display(HTML("<h1>Sample html</h1>"))',
|
||||
outputs: List.of({
|
||||
data: Object.freeze({
|
||||
data: {
|
||||
"text/html": "<h1>Sample output</h1>",
|
||||
"text/plain": "<IPython.core.display.HTML object>"
|
||||
}
|
||||
} as MediaBundle),
|
||||
output_type: "display_data",
|
||||
metadata: undefined
|
||||
})
|
||||
} as CodeCellParams),
|
||||
"3": makeCodeCell({
|
||||
cell_type: "code",
|
||||
execution_count: undefined,
|
||||
metadata: undefined,
|
||||
source: 'print("hello world")',
|
||||
outputs: List.of({
|
||||
name: "stdout",
|
||||
output_type: "stream",
|
||||
text: "hello world"
|
||||
})
|
||||
} as CodeCellParams)
|
||||
}),
|
||||
nbformat_minor: 2,
|
||||
nbformat: 2,
|
||||
metadata: undefined
|
||||
});
|
||||
|
||||
describe("NotebookUtil", () => {
|
||||
describe("isNotebookFile", () => {
|
||||
@@ -46,4 +107,11 @@ describe("NotebookUtil", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findFirstCodeCellWithDisplay", () => {
|
||||
it("works for Notebook file", () => {
|
||||
const notebookObject = notebookRecord as ImmutableNotebook;
|
||||
expect(NotebookUtil.findFirstCodeCellWithDisplay(notebookObject)).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path";
|
||||
import { ImmutableNotebook } from "@nteract/commutable";
|
||||
import { ImmutableNotebook, ImmutableCodeCell, ImmutableOutput } from "@nteract/commutable";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
import { StringUtils } from "../../Utils/StringUtils";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
@@ -100,4 +100,30 @@ export class NotebookUtil {
|
||||
const basePath = path.split(contentName).shift();
|
||||
return `${basePath}${newName}`;
|
||||
}
|
||||
|
||||
public static findFirstCodeCellWithDisplay(notebookObject: ImmutableNotebook): number {
|
||||
let codeCellCount = -1;
|
||||
for (let i = 0; i < notebookObject.cellOrder.size; i++) {
|
||||
const cellId = notebookObject.cellOrder.get(i);
|
||||
if (cellId) {
|
||||
const cell = notebookObject.cellMap.get(cellId);
|
||||
if (cell && cell.cell_type === "code") {
|
||||
codeCellCount++;
|
||||
const codeCell = cell as ImmutableCodeCell;
|
||||
if (codeCell.outputs) {
|
||||
const displayOutput = codeCell.outputs.find((output: ImmutableOutput) => {
|
||||
if (output.output_type === "display_data" || output.output_type === "execute_result") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (displayOutput) {
|
||||
return codeCellCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error("Output does not exist for any of the cells.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user