mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
* Add Security Warning Bar for untrusted notebooks * Update * Another update * Add a snapshot test * UX updates * More updates * Add tests * Update test snapshot * Update string * Update security message
238 lines
8.3 KiB
TypeScript
238 lines
8.3 KiB
TypeScript
import { ContextualMenuItemType, DirectionalHint, IconButton, IContextualMenuItem } from "@fluentui/react";
|
|
import { CellId, CellType, ImmutableCodeCell } from "@nteract/commutable";
|
|
import { actions, AppState, DocumentRecordProps } from "@nteract/core";
|
|
import * as selectors from "@nteract/selectors";
|
|
import { CellToolbarContext } from "@nteract/stateful-components";
|
|
import { ContentRef } from "@nteract/types";
|
|
import { RecordOf } from "immutable";
|
|
import * as React from "react";
|
|
import { connect } from "react-redux";
|
|
import { Dispatch } from "redux";
|
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
import * as cdbActions from "../NotebookComponent/actions";
|
|
import { SnapshotRequest } from "../NotebookComponent/types";
|
|
import { NotebookUtil } from "../NotebookUtil";
|
|
|
|
export interface ComponentProps {
|
|
contentRef: ContentRef;
|
|
id: CellId;
|
|
}
|
|
|
|
interface DispatchProps {
|
|
executeCell: () => void;
|
|
insertCodeCellAbove: () => void;
|
|
insertCodeCellBelow: () => void;
|
|
insertTextCellAbove: () => void;
|
|
insertTextCellBelow: () => void;
|
|
moveCell: (destinationId: CellId, above: boolean) => void;
|
|
clearOutputs: () => void;
|
|
deleteCell: () => void;
|
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) => void;
|
|
takeNotebookSnapshot: (payload: SnapshotRequest) => void;
|
|
}
|
|
|
|
interface StateProps {
|
|
cellType: CellType;
|
|
cellIdAbove: CellId;
|
|
cellIdBelow: CellId;
|
|
hasCodeOutput: boolean;
|
|
isNotebookUntrusted: boolean;
|
|
}
|
|
|
|
class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & StateProps> {
|
|
static contextType = CellToolbarContext;
|
|
|
|
render(): JSX.Element {
|
|
let items: IContextualMenuItem[] = [];
|
|
const isNotebookUntrusted = this.props.isNotebookUntrusted;
|
|
const runTooltip = isNotebookUntrusted ? NotebookUtil.UntrustedNotebookRunHint : undefined;
|
|
|
|
if (this.props.cellType === "code") {
|
|
items = items.concat([
|
|
{
|
|
key: "Run",
|
|
text: "Run",
|
|
title: runTooltip,
|
|
disabled: isNotebookUntrusted,
|
|
onClick: () => {
|
|
this.props.executeCell();
|
|
this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark);
|
|
},
|
|
},
|
|
{
|
|
key: "Clear Outputs",
|
|
text: "Clear Outputs",
|
|
onClick: () => {
|
|
this.props.clearOutputs();
|
|
this.props.traceNotebookTelemetry(Action.NotebooksClearOutputsFromMenu, ActionModifiers.Mark);
|
|
},
|
|
},
|
|
]);
|
|
|
|
if (this.props.hasCodeOutput) {
|
|
items.push({
|
|
key: "Export output to image",
|
|
text: "Export output to image",
|
|
onClick: () => {
|
|
this.props.takeNotebookSnapshot({
|
|
requestId: new Date().getTime().toString(),
|
|
aspectRatio: undefined,
|
|
type: "celloutput",
|
|
cellId: this.props.id,
|
|
notebookContentRef: this.props.contentRef,
|
|
downloadFilename: `celloutput-${this.props.contentRef}_${this.props.id}.png`,
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
items.push({
|
|
key: "Divider",
|
|
itemType: ContextualMenuItemType.Divider,
|
|
});
|
|
}
|
|
|
|
items = items.concat([
|
|
{
|
|
key: "Divider2",
|
|
itemType: ContextualMenuItemType.Divider,
|
|
},
|
|
{
|
|
key: "Insert Code Cell Above",
|
|
text: "Insert Code Cell Above",
|
|
onClick: () => {
|
|
this.props.insertCodeCellAbove();
|
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertCodeCellAboveFromMenu, ActionModifiers.Mark);
|
|
},
|
|
},
|
|
{
|
|
key: "Insert Code Cell Below",
|
|
text: "Insert Code Cell Below",
|
|
onClick: () => {
|
|
this.props.insertCodeCellBelow();
|
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertCodeCellBelowFromMenu, ActionModifiers.Mark);
|
|
},
|
|
},
|
|
{
|
|
key: "Insert Text Cell Above",
|
|
text: "Insert Text Cell Above",
|
|
onClick: () => {
|
|
this.props.insertTextCellAbove();
|
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertTextCellAboveFromMenu, ActionModifiers.Mark);
|
|
},
|
|
},
|
|
{
|
|
key: "Insert Text Cell Below",
|
|
text: "Insert Text Cell Below",
|
|
onClick: () => {
|
|
this.props.insertTextCellBelow();
|
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertTextCellBelowFromMenu, ActionModifiers.Mark);
|
|
},
|
|
},
|
|
{
|
|
key: "Divider3",
|
|
itemType: ContextualMenuItemType.Divider,
|
|
},
|
|
]);
|
|
|
|
const moveItems: IContextualMenuItem[] = [];
|
|
if (this.props.cellIdAbove !== undefined) {
|
|
moveItems.push({
|
|
key: "Move Cell Up",
|
|
text: "Move Cell Up",
|
|
onClick: () => {
|
|
this.props.moveCell(this.props.cellIdAbove, true);
|
|
this.props.traceNotebookTelemetry(Action.NotebooksMoveCellUpFromMenu, ActionModifiers.Mark);
|
|
},
|
|
});
|
|
}
|
|
|
|
if (this.props.cellIdBelow !== undefined) {
|
|
moveItems.push({
|
|
key: "Move Cell Down",
|
|
text: "Move Cell Down",
|
|
onClick: () => {
|
|
this.props.moveCell(this.props.cellIdBelow, false);
|
|
this.props.traceNotebookTelemetry(Action.NotebooksMoveCellDownFromMenu, ActionModifiers.Mark);
|
|
},
|
|
});
|
|
}
|
|
|
|
if (moveItems.length > 0) {
|
|
moveItems.push({
|
|
key: "Divider4",
|
|
itemType: ContextualMenuItemType.Divider,
|
|
});
|
|
items = items.concat(moveItems);
|
|
}
|
|
|
|
items.push({
|
|
key: "Delete Cell",
|
|
text: "Delete Cell",
|
|
onClick: () => {
|
|
this.props.deleteCell();
|
|
this.props.traceNotebookTelemetry(Action.DeleteCellFromMenu, ActionModifiers.Mark);
|
|
},
|
|
});
|
|
|
|
const menuItemLabel = "More";
|
|
return (
|
|
<IconButton
|
|
name="More"
|
|
className="CellContextMenuButton"
|
|
ariaLabel={menuItemLabel}
|
|
menuIconProps={{
|
|
iconName: menuItemLabel,
|
|
styles: { root: { fontSize: "18px", fontWeight: "bold" } },
|
|
}}
|
|
menuProps={{
|
|
isBeakVisible: false,
|
|
directionalHint: DirectionalHint.bottomRightEdge,
|
|
items,
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
const mapDispatchToProps = (
|
|
dispatch: Dispatch,
|
|
{ id, contentRef }: { id: CellId; contentRef: ContentRef }
|
|
): DispatchProps => ({
|
|
executeCell: () => dispatch(actions.executeCell({ id, contentRef })),
|
|
insertCodeCellAbove: () => dispatch(actions.createCellAbove({ id, contentRef, cellType: "code" })),
|
|
insertCodeCellBelow: () => dispatch(actions.createCellBelow({ id, contentRef, cellType: "code" })),
|
|
insertTextCellAbove: () => dispatch(actions.createCellAbove({ id, contentRef, cellType: "markdown" })),
|
|
insertTextCellBelow: () => dispatch(actions.createCellBelow({ id, contentRef, cellType: "markdown" })),
|
|
moveCell: (destinationId: CellId, above: boolean) =>
|
|
dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
|
|
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
|
|
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
|
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) =>
|
|
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })),
|
|
takeNotebookSnapshot: (request: SnapshotRequest) => dispatch(cdbActions.takeNotebookSnapshot(request)),
|
|
});
|
|
|
|
const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state: AppState) => StateProps) => {
|
|
const mapStateToProps = (state: AppState) => {
|
|
const cell = selectors.cell.cellFromState(state, { id: ownProps.id, contentRef: ownProps.contentRef });
|
|
const cellType = cell.cell_type;
|
|
const model = selectors.model(state, { contentRef: ownProps.contentRef });
|
|
const cellOrder = selectors.notebook.cellOrder(model as RecordOf<DocumentRecordProps>);
|
|
const cellIndex = cellOrder.indexOf(ownProps.id);
|
|
const cellIdAbove = cellIndex ? cellOrder.get(cellIndex - 1, undefined) : undefined;
|
|
const cellIdBelow = cellIndex !== undefined ? cellOrder.get(cellIndex + 1, undefined) : undefined;
|
|
|
|
return {
|
|
cellType,
|
|
cellIdAbove,
|
|
cellIdBelow,
|
|
hasCodeOutput: cellType === "code" && NotebookUtil.hasCodeCellOutput(cell as ImmutableCodeCell),
|
|
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, ownProps.contentRef),
|
|
};
|
|
};
|
|
return mapStateToProps;
|
|
};
|
|
|
|
export default connect(makeMapStateToProps, mapDispatchToProps)(BaseToolbar);
|