mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-23 11:44:03 +00:00
Initial Move from Azure DevOps to GitHub
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
|
||||
import NotebookRenderer from "../../../NotebookRenderer/NotebookRenderer";
|
||||
import * as TextFile from "./text-file";
|
||||
|
||||
const PaddedContainer = styled.div`
|
||||
padding-left: var(--nt-spacing-l, 10px);
|
||||
padding-top: var(--nt-spacing-m, 10px);
|
||||
padding-right: var(--nt-spacing-m, 10px);
|
||||
`;
|
||||
|
||||
const JupyterExtensionContainer = styled.div`
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const JupyterExtensionChoiceContainer = styled.div`
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
interface FileProps {
|
||||
type: "notebook" | "file" | "dummy";
|
||||
contentRef: ContentRef;
|
||||
mimetype?: string | null;
|
||||
}
|
||||
|
||||
export class File extends React.PureComponent<FileProps> {
|
||||
getChoice = () => {
|
||||
let choice = null;
|
||||
|
||||
// notebooks don't report a mimetype so we'll use the content.type
|
||||
if (this.props.type === "notebook") {
|
||||
choice = <NotebookRenderer contentRef={this.props.contentRef} />;
|
||||
} else if (this.props.type === "dummy") {
|
||||
choice = null;
|
||||
} else if (this.props.mimetype == null || !TextFile.handles(this.props.mimetype)) {
|
||||
// This should not happen as we intercept mimetype upstream, but just in case
|
||||
choice = (
|
||||
<PaddedContainer>
|
||||
<pre>
|
||||
This file type cannot be rendered. Please download the file, in order to view it outside of Data Explorer.
|
||||
</pre>
|
||||
</PaddedContainer>
|
||||
);
|
||||
} else {
|
||||
choice = <TextFile.default contentRef={this.props.contentRef} />;
|
||||
}
|
||||
|
||||
return choice;
|
||||
};
|
||||
|
||||
render(): JSX.Element {
|
||||
const choice = this.getChoice();
|
||||
|
||||
// Right now we only handle one kind of editor
|
||||
// If/when we support more modes, we would case them off here
|
||||
return (
|
||||
<React.Fragment>
|
||||
<JupyterExtensionContainer>
|
||||
<JupyterExtensionChoiceContainer>{choice}</JupyterExtensionChoiceContainer>
|
||||
</JupyterExtensionContainer>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface InitialProps {
|
||||
contentRef: ContentRef;
|
||||
}
|
||||
|
||||
// Since the contentRef stays unique for the duration of this file,
|
||||
// we use the makeMapStateToProps pattern to optimize re-render
|
||||
const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps) => {
|
||||
const { contentRef } = initialProps;
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
const content = selectors.content(state, initialProps);
|
||||
|
||||
return {
|
||||
contentRef,
|
||||
mimetype: content.mimetype,
|
||||
type: content.type
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export const ConnectedFile = connect(makeMapStateToProps)(File);
|
||||
|
||||
export default ConnectedFile;
|
||||
@@ -0,0 +1,143 @@
|
||||
import { StringUtils } from "../../../../../Utils/StringUtils";
|
||||
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import { MonacoEditorProps } from "@nteract/monaco-editor";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
import styled from "styled-components";
|
||||
|
||||
const EditorContainer = styled.div`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.monaco {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
interface MappedStateProps {
|
||||
mimetype: string;
|
||||
text: string;
|
||||
contentRef: ContentRef;
|
||||
theme: string; // "light" | "dark";
|
||||
}
|
||||
|
||||
interface MappedDispatchProps {
|
||||
handleChange: (value: string) => void;
|
||||
}
|
||||
|
||||
type TextFileProps = MappedStateProps & MappedDispatchProps;
|
||||
|
||||
interface TextFileState {
|
||||
Editor: React.ComponentType<MonacoEditorProps>;
|
||||
}
|
||||
|
||||
class EditorPlaceholder extends React.PureComponent<MonacoEditorProps> {
|
||||
render(): JSX.Element {
|
||||
// TODO: Show a little blocky placeholder
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class TextFile extends React.PureComponent<TextFileProps, TextFileState> {
|
||||
constructor(props: TextFileProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
Editor: EditorPlaceholder
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = (source: string) => {
|
||||
this.props.handleChange(source);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
import(/* webpackChunkName: "monaco-editor" */ "@nteract/monaco-editor").then(module => {
|
||||
this.setState({ Editor: module.default });
|
||||
});
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const Editor = this.state.Editor;
|
||||
|
||||
return (
|
||||
<EditorContainer className="nteract-editor" style={{ position: "static" }}>
|
||||
<Editor
|
||||
theme={this.props.theme === "dark" ? "vs-dark" : "vs"}
|
||||
mode={this.props.mimetype}
|
||||
editorFocused
|
||||
value={this.props.text}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
/>
|
||||
</EditorContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface InitialProps {
|
||||
contentRef: ContentRef;
|
||||
}
|
||||
|
||||
function makeMapStateToTextFileProps(
|
||||
initialState: AppState,
|
||||
initialProps: InitialProps
|
||||
): (state: AppState) => MappedStateProps {
|
||||
const { contentRef } = initialProps;
|
||||
|
||||
const mapStateToTextFileProps = (state: AppState) => {
|
||||
const content = selectors.content(state, { contentRef });
|
||||
if (!content || content.type !== "file") {
|
||||
throw new Error("The text file component must have content");
|
||||
}
|
||||
|
||||
const text = content.model ? content.model.text : "";
|
||||
|
||||
return {
|
||||
contentRef,
|
||||
mimetype: content.mimetype != null ? content.mimetype : "text/plain",
|
||||
text,
|
||||
theme: selectors.currentTheme(state)
|
||||
};
|
||||
};
|
||||
return mapStateToTextFileProps;
|
||||
}
|
||||
|
||||
const makeMapDispatchToTextFileProps = (
|
||||
initialDispatch: Dispatch,
|
||||
initialProps: InitialProps
|
||||
): ((dispatch: Dispatch) => MappedDispatchProps) => {
|
||||
const { contentRef } = initialProps;
|
||||
|
||||
const mapDispatchToTextFileProps = (dispatch: Dispatch) => {
|
||||
return {
|
||||
handleChange: (source: string) => {
|
||||
dispatch(
|
||||
actions.updateFileText({
|
||||
contentRef,
|
||||
text: source
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
return mapDispatchToTextFileProps;
|
||||
};
|
||||
|
||||
const ConnectedTextFile = connect<MappedStateProps, MappedDispatchProps, InitialProps, AppState>(
|
||||
makeMapStateToTextFileProps,
|
||||
makeMapDispatchToTextFileProps
|
||||
)(TextFile);
|
||||
|
||||
export function handles(mimetype: string) {
|
||||
return (
|
||||
!mimetype ||
|
||||
StringUtils.startsWith(mimetype, "text/") ||
|
||||
StringUtils.startsWith(mimetype, "application/javascript") ||
|
||||
StringUtils.startsWith(mimetype, "application/json") ||
|
||||
StringUtils.startsWith(mimetype, "application/x-ipynb+json")
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectedTextFile;
|
||||
173
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
Normal file
173
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
// Vendor modules
|
||||
import { CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||
import { HeaderDataProps } from "@nteract/connected-components/lib/header-editor";
|
||||
import {
|
||||
AppState,
|
||||
ContentRef,
|
||||
HostRecord,
|
||||
selectors,
|
||||
actions,
|
||||
DirectoryContentRecordProps,
|
||||
DummyContentRecordProps,
|
||||
FileContentRecordProps,
|
||||
NotebookContentRecordProps
|
||||
} from "@nteract/core";
|
||||
import { RecordOf } from "immutable";
|
||||
import * as React from "react";
|
||||
import { HotKeys, KeyMap } from "react-hotkeys";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
|
||||
// Local modules
|
||||
import { default as File } from "./file";
|
||||
|
||||
interface IContentsBaseProps {
|
||||
contentRef: ContentRef;
|
||||
error?: object | null;
|
||||
}
|
||||
|
||||
interface IStateToProps {
|
||||
headerData?: HeaderDataProps;
|
||||
}
|
||||
|
||||
interface IDispatchFromProps {
|
||||
handlers?: any;
|
||||
onHeaderEditorChange?: (props: HeaderDataProps) => void;
|
||||
}
|
||||
|
||||
type ContentsProps = IContentsBaseProps & IStateToProps & IDispatchFromProps;
|
||||
|
||||
class Contents extends React.PureComponent<ContentsProps> {
|
||||
private keyMap: KeyMap = {
|
||||
CHANGE_CELL_TYPE: ["ctrl+shift+y", "ctrl+shift+m", "meta+shift+y", "meta+shift+m"],
|
||||
COPY_CELL: ["ctrl+shift+c", "meta+shift+c"],
|
||||
CREATE_CELL_ABOVE: ["ctrl+shift+a", "meta+shift+a"],
|
||||
CREATE_CELL_BELOW: ["ctrl+shift+b", "meta+shift+b"],
|
||||
CUT_CELL: ["ctrl+shift+x", "meta+shift+x"],
|
||||
DELETE_CELL: ["ctrl+shift+d", "meta+shift+d"],
|
||||
EXECUTE_ALL_CELLS: ["alt+r a"],
|
||||
INTERRUPT_KERNEL: ["alt+r i"],
|
||||
KILL_KERNEL: ["alt+r k"],
|
||||
OPEN: ["ctrl+o", "meta+o"],
|
||||
PASTE_CELL: ["ctrl+shift+v"],
|
||||
RESTART_KERNEL: ["alt+r r", "alt+r c", "alt+r a"],
|
||||
SAVE: ["ctrl+s", "ctrl+shift+s", "meta+s", "meta+shift+s"]
|
||||
};
|
||||
|
||||
render(): JSX.Element {
|
||||
const { contentRef, handlers } = this.props;
|
||||
|
||||
if (!contentRef) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<HotKeys keyMap={this.keyMap} handlers={handlers} className="hotKeys">
|
||||
<File contentRef={contentRef} />
|
||||
</HotKeys>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const makeMapStateToProps: any = (initialState: AppState, initialProps: { contentRef: ContentRef }) => {
|
||||
const host: HostRecord = initialState.app.host;
|
||||
|
||||
if (host.type !== "jupyter") {
|
||||
throw new Error("this component only works with jupyter apps");
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState): Partial<ContentsProps> => {
|
||||
const contentRef: ContentRef = initialProps.contentRef;
|
||||
|
||||
if (!contentRef) {
|
||||
throw new Error("cant display without a contentRef");
|
||||
}
|
||||
|
||||
const content:
|
||||
| RecordOf<NotebookContentRecordProps>
|
||||
| RecordOf<DummyContentRecordProps>
|
||||
| RecordOf<FileContentRecordProps>
|
||||
| RecordOf<DirectoryContentRecordProps>
|
||||
| undefined = selectors.content(state, { contentRef });
|
||||
|
||||
if (!content) {
|
||||
return {
|
||||
contentRef: undefined,
|
||||
error: undefined,
|
||||
headerData: undefined
|
||||
};
|
||||
}
|
||||
|
||||
let headerData: HeaderDataProps = {
|
||||
authors: [],
|
||||
description: "",
|
||||
tags: [],
|
||||
title: ""
|
||||
};
|
||||
|
||||
// If a notebook, we need to read in the headerData if available
|
||||
if (content.type === "notebook") {
|
||||
const notebook: ImmutableNotebook = content.model.get("notebook");
|
||||
const metadata: any = notebook.metadata.toJS();
|
||||
const { authors = [], description = "", tags = [], title = "" } = metadata;
|
||||
|
||||
// Updates
|
||||
headerData = Object.assign({}, headerData, {
|
||||
authors,
|
||||
description,
|
||||
tags,
|
||||
title
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
contentRef,
|
||||
error: content.error,
|
||||
headerData
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: ContentsProps): object => {
|
||||
const { contentRef } = ownProps;
|
||||
|
||||
return {
|
||||
onHeaderEditorChange: (props: HeaderDataProps) => {
|
||||
return dispatch(
|
||||
actions.overwriteMetadataFields({
|
||||
...props,
|
||||
contentRef: ownProps.contentRef
|
||||
})
|
||||
);
|
||||
},
|
||||
// `HotKeys` handlers object
|
||||
// see: https://github.com/greena13/react-hotkeys#defining-handlers
|
||||
handlers: {
|
||||
CHANGE_CELL_TYPE: (event: KeyboardEvent) => {
|
||||
const type: CellType = event.key === "Y" ? "code" : "markdown";
|
||||
return dispatch(actions.changeCellType({ to: type, contentRef }));
|
||||
},
|
||||
COPY_CELL: () => dispatch(actions.copyCell({ contentRef })),
|
||||
CREATE_CELL_ABOVE: () => dispatch(actions.createCellAbove({ cellType: "code", contentRef })),
|
||||
CREATE_CELL_BELOW: () => dispatch(actions.createCellBelow({ cellType: "code", source: "", contentRef })),
|
||||
CUT_CELL: () => dispatch(actions.cutCell({ contentRef })),
|
||||
DELETE_CELL: () => dispatch(actions.deleteCell({ contentRef })),
|
||||
EXECUTE_ALL_CELLS: () => dispatch(actions.executeAllCells({ contentRef })),
|
||||
INTERRUPT_KERNEL: () => dispatch(actions.interruptKernel({})),
|
||||
KILL_KERNEL: () => dispatch(actions.killKernel({ restarting: false })),
|
||||
PASTE_CELL: () => dispatch(actions.pasteCell({ contentRef })),
|
||||
RESTART_KERNEL: (event: KeyboardEvent) => {
|
||||
const outputHandling: "None" | "Clear All" | "Run All" =
|
||||
event.key === "r" ? "None" : event.key === "a" ? "Run All" : "Clear All";
|
||||
return dispatch(actions.restartKernel({ outputHandling, contentRef }));
|
||||
},
|
||||
SAVE: () => dispatch(actions.save({ contentRef }))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(Contents);
|
||||
Reference in New Issue
Block a user