mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
Fix bug publish screenshot (#762)
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/762?feature.someFeatureFlagYouMightNeed=true) The main change in this PR fixes the snapshot functionality in the Publish pane-related components. Because the code cell outputs are now rendered in their own iframes for security reasons, a single snapshot of the notebook is no longer possible: each cell output takes its own snapshot and the snapshots are collated on the main notebook snapshot. - Move the snapshot functionality to notebook components: this removes the reference of the notebook DOM node that we must pass to the Publish pane via explorer. - Add slice in the state and actions in notebook redux for notebook snapshot requests and result - Add post robot message to take snapshots and receive results - Add logic in `NotebookRenderer` to wait for all output snapshots done before taking the main one collating. - Use `zustand` to share snapshot between Redux world and React world. This solves the issue of keeping the `PanelContainer` component generic, while being able to update its children (`PublishPanel` component) with the new snapshot. Additional changes: - Add `local()` in `@font-face` to check if font is already installed before downloading the font (must be done for Safari, but not Edge/Chrome) - Add "Export output to image" menu item in notebook cell, since each cell output can take its own snapshot (which can be downloaded) 
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||
|
||||
// Vendor modules
|
||||
import { actions, createContentRef, createKernelRef, selectors } from "@nteract/core";
|
||||
import VirtualCommandBarComponent from "./VirtualCommandBarComponent";
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||
import { NotebookContentItem } from "../NotebookContentItem";
|
||||
import { NotebookComponentBootstrapper } from "./NotebookComponentBootstrapper";
|
||||
import { CdbAppState } from "./types";
|
||||
import VirtualCommandBarComponent from "./VirtualCommandBarComponent";
|
||||
|
||||
export interface NotebookComponentAdapterOptions {
|
||||
contentItem: NotebookContentItem;
|
||||
@@ -19,7 +16,6 @@ export interface NotebookComponentAdapterOptions {
|
||||
|
||||
export class NotebookComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
||||
private onUpdateKernelInfo: () => void;
|
||||
public getNotebookParentElement: () => HTMLElement;
|
||||
public parameters: any;
|
||||
|
||||
constructor(options: NotebookComponentAdapterOptions) {
|
||||
@@ -46,11 +42,6 @@ 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 => {
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { NotebookComponent } from "./NotebookComponent";
|
||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
|
||||
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||
// Vendor modules
|
||||
import {
|
||||
actions,
|
||||
AppState,
|
||||
createKernelRef,
|
||||
DocumentRecordProps,
|
||||
ContentRef,
|
||||
DocumentRecordProps,
|
||||
KernelRef,
|
||||
NotebookContentRecord,
|
||||
selectors,
|
||||
} from "@nteract/core";
|
||||
import * as Immutable from "immutable";
|
||||
import { Provider } from "react-redux";
|
||||
import { CellType, CellId, ImmutableNotebook } from "@nteract/commutable";
|
||||
import { Store, AnyAction } from "redux";
|
||||
|
||||
import "./NotebookComponent.less";
|
||||
|
||||
import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "@nteract/styles/editor-overrides.css";
|
||||
import "@nteract/styles/global-variables.css";
|
||||
import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import * as Immutable from "immutable";
|
||||
import * as React from "react";
|
||||
import { Provider } from "react-redux";
|
||||
import "react-table/react-table.css";
|
||||
|
||||
import * as CdbActions from "./actions";
|
||||
import { AnyAction, Store } from "redux";
|
||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
import * as NteractUtil from "../NTeractUtil";
|
||||
import * as CdbActions from "./actions";
|
||||
import { NotebookComponent } from "./NotebookComponent";
|
||||
import "./NotebookComponent.less";
|
||||
|
||||
export interface NotebookComponentBootstrapperOptions {
|
||||
notebookClient: NotebookClientV2;
|
||||
@@ -37,7 +31,7 @@ export interface NotebookComponentBootstrapperOptions {
|
||||
}
|
||||
|
||||
export class NotebookComponentBootstrapper {
|
||||
protected contentRef: ContentRef;
|
||||
public contentRef: ContentRef;
|
||||
protected renderExtraComponent: () => JSX.Element;
|
||||
|
||||
private notebookClient: NotebookClientV2;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CellId } from "@nteract/commutable";
|
||||
import { ContentRef } from "@nteract/core";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { SnapshotFragment, SnapshotRequest } from "./types";
|
||||
|
||||
export const CLOSE_NOTEBOOK = "CLOSE_NOTEBOOK";
|
||||
export interface CloseNotebookAction {
|
||||
@@ -85,21 +86,68 @@ export const traceNotebookTelemetry = (payload: {
|
||||
};
|
||||
};
|
||||
|
||||
export const UPDATE_NOTEBOOK_PARENT_DOM_ELTS = "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
|
||||
export interface UpdateNotebookParentDomEltAction {
|
||||
type: "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
|
||||
export const STORE_CELL_OUTPUT_SNAPSHOT = "STORE_CELL_OUTPUT_SNAPSHOT";
|
||||
export interface StoreCellOutputSnapshotAction {
|
||||
type: "STORE_CELL_OUTPUT_SNAPSHOT";
|
||||
payload: {
|
||||
contentRef: ContentRef;
|
||||
parentElt: HTMLElement;
|
||||
cellId: string;
|
||||
snapshot: SnapshotFragment;
|
||||
};
|
||||
}
|
||||
|
||||
export const UpdateNotebookParentDomElt = (payload: {
|
||||
contentRef: ContentRef;
|
||||
parentElt: HTMLElement;
|
||||
}): UpdateNotebookParentDomEltAction => {
|
||||
export const storeCellOutputSnapshot = (payload: {
|
||||
cellId: string;
|
||||
snapshot: SnapshotFragment;
|
||||
}): StoreCellOutputSnapshotAction => {
|
||||
return {
|
||||
type: UPDATE_NOTEBOOK_PARENT_DOM_ELTS,
|
||||
type: STORE_CELL_OUTPUT_SNAPSHOT,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
export const STORE_NOTEBOOK_SNAPSHOT = "STORE_NOTEBOOK_SNAPSHOT";
|
||||
export interface StoreNotebookSnapshotAction {
|
||||
type: "STORE_NOTEBOOK_SNAPSHOT";
|
||||
payload: {
|
||||
imageSrc: string;
|
||||
requestId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const storeNotebookSnapshot = (payload: {
|
||||
imageSrc: string;
|
||||
requestId: string;
|
||||
}): StoreNotebookSnapshotAction => {
|
||||
return {
|
||||
type: STORE_NOTEBOOK_SNAPSHOT,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
export const TAKE_NOTEBOOK_SNAPSHOT = "TAKE_NOTEBOOK_SNAPSHOT";
|
||||
export interface TakeNotebookSnapshotAction {
|
||||
type: "TAKE_NOTEBOOK_SNAPSHOT";
|
||||
payload: SnapshotRequest;
|
||||
}
|
||||
|
||||
export const takeNotebookSnapshot = (payload: SnapshotRequest): TakeNotebookSnapshotAction => {
|
||||
return {
|
||||
type: TAKE_NOTEBOOK_SNAPSHOT,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
export const NOTEBOOK_SNAPSHOT_ERROR = "NOTEBOOK_SNAPSHOT_ERROR";
|
||||
export interface NotebookSnapshotErrorAction {
|
||||
type: "NOTEBOOK_SNAPSHOT_ERROR";
|
||||
payload: {
|
||||
error: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const notebookSnapshotError = (payload: { error: string }): NotebookSnapshotErrorAction => {
|
||||
return {
|
||||
type: NOTEBOOK_SNAPSHOT_ERROR,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -70,17 +70,32 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
|
||||
return state.set("hoveredCellId", typedAction.payload.cellId);
|
||||
}
|
||||
|
||||
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);
|
||||
case cdbActions.STORE_CELL_OUTPUT_SNAPSHOT: {
|
||||
const typedAction = action as cdbActions.StoreCellOutputSnapshotAction;
|
||||
state.cellOutputSnapshots.set(typedAction.payload.cellId, typedAction.payload.snapshot);
|
||||
// TODO Simpler datastructure to instantiate new Map?
|
||||
return state.set("cellOutputSnapshots", new Map(state.cellOutputSnapshots));
|
||||
}
|
||||
|
||||
case cdbActions.STORE_NOTEBOOK_SNAPSHOT: {
|
||||
const typedAction = action as cdbActions.StoreNotebookSnapshotAction;
|
||||
// Clear pending request
|
||||
return state.set("notebookSnapshot", typedAction.payload).set("pendingSnapshotRequest", undefined);
|
||||
}
|
||||
|
||||
case cdbActions.TAKE_NOTEBOOK_SNAPSHOT: {
|
||||
const typedAction = action as cdbActions.TakeNotebookSnapshotAction;
|
||||
// Clear previous snapshots
|
||||
return state
|
||||
.set("cellOutputSnapshots", new Map())
|
||||
.set("notebookSnapshot", undefined)
|
||||
.set("notebookSnapshotError", undefined)
|
||||
.set("pendingSnapshotRequest", typedAction.payload);
|
||||
}
|
||||
|
||||
case cdbActions.NOTEBOOK_SNAPSHOT_ERROR: {
|
||||
const typedAction = action as cdbActions.NotebookSnapshotErrorAction;
|
||||
return state.set("notebookSnapshotError", typedAction.payload.error);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
|
||||
@@ -1,15 +1,40 @@
|
||||
import * as Immutable from "immutable";
|
||||
import { AppState, ContentRef } from "@nteract/core";
|
||||
|
||||
import { Notebook } from "../../../Common/Constants";
|
||||
import { CellId } from "@nteract/commutable";
|
||||
import { AppState } from "@nteract/core";
|
||||
import * as Immutable from "immutable";
|
||||
import { Notebook } from "../../../Common/Constants";
|
||||
|
||||
export interface SnapshotFragment {
|
||||
image: HTMLImageElement;
|
||||
boundingClientRect: DOMRect;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export type SnapshotRequest = NotebookSnapshotRequest | CellSnapshotRequest;
|
||||
interface NotebookSnapshotRequestBase {
|
||||
requestId: string;
|
||||
aspectRatio: number;
|
||||
notebookContentRef: string; // notebook redux contentRef
|
||||
downloadFilename?: string; // Optional: will download as a file
|
||||
}
|
||||
|
||||
interface NotebookSnapshotRequest extends NotebookSnapshotRequestBase {
|
||||
type: "notebook";
|
||||
}
|
||||
|
||||
interface CellSnapshotRequest extends NotebookSnapshotRequestBase {
|
||||
type: "celloutput";
|
||||
cellId: string;
|
||||
}
|
||||
|
||||
export interface CdbRecordProps {
|
||||
databaseAccountName: string | undefined;
|
||||
defaultExperience: string | undefined;
|
||||
kernelRestartDelayMs: number;
|
||||
hoveredCellId: CellId | undefined;
|
||||
currentNotebookParentElements: Map<ContentRef, HTMLElement>;
|
||||
cellOutputSnapshots: Map<string, SnapshotFragment>;
|
||||
notebookSnapshot?: { imageSrc: string; requestId: string };
|
||||
pendingSnapshotRequest?: SnapshotRequest;
|
||||
notebookSnapshotError?: string;
|
||||
}
|
||||
|
||||
export type CdbRecord = Immutable.RecordOf<CdbRecordProps>;
|
||||
@@ -23,5 +48,8 @@ export const makeCdbRecord = Immutable.Record<CdbRecordProps>({
|
||||
defaultExperience: undefined,
|
||||
kernelRestartDelayMs: Notebook.kernelRestartInitialDelayMs,
|
||||
hoveredCellId: undefined,
|
||||
currentNotebookParentElements: new Map<ContentRef, HTMLElement>(),
|
||||
cellOutputSnapshots: new Map(),
|
||||
notebookSnapshot: undefined,
|
||||
pendingSnapshotRequest: undefined,
|
||||
notebookSnapshotError: undefined,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user