From 086533cfd9c865977901d0112ce6d341ac5b3c81 Mon Sep 17 00:00:00 2001 From: William Stein Date: Fri, 15 Mar 2019 23:09:33 +0000 Subject: [PATCH 01/29] jupyter; finish converting cell-output-message to typescript --- src/smc-webapp/jupyter/actions.ts | 4 +- src/smc-webapp/jupyter/cell-output-iframe.tsx | 6 + .../jupyter/cell-output-message.tsx | 406 ++++++++---------- src/smc-webapp/jupyter/javascript-eval.ts | 6 +- 4 files changed, 194 insertions(+), 228 deletions(-) diff --git a/src/smc-webapp/jupyter/actions.ts b/src/smc-webapp/jupyter/actions.ts index c30ff3d11d..ef54a8a570 100644 --- a/src/smc-webapp/jupyter/actions.ts +++ b/src/smc-webapp/jupyter/actions.ts @@ -119,13 +119,13 @@ export class JupyterActions extends Actions { public syncdb: any; public util: any; // TODO: check if this is used publicly - _init = async ( + _init = ( project_id: string, path: string, syncdb: any, store: any, client: any - ) => { + ): void => { if (project_id == null || path == null) { // typescript should ensure this, but just in case. throw Error("type error -- project_id and path can't be null"); diff --git a/src/smc-webapp/jupyter/cell-output-iframe.tsx b/src/smc-webapp/jupyter/cell-output-iframe.tsx index f20ed18766..f62e073045 100644 --- a/src/smc-webapp/jupyter/cell-output-iframe.tsx +++ b/src/smc-webapp/jupyter/cell-output-iframe.tsx @@ -20,19 +20,23 @@ export interface IFrameState { export class IFrame extends Component { private timeout: any; // TODO: WARNING: check this - its a different pattern than the original component, see https://github.com/facebook/react/issues/5465 + constructor(props: IFrameProps, context: any) { super(props, context); this.state = { attempts: 0, show: false }; } + clearTimeout = () => { if (this.timeout !== undefined) { clearTimeout(this.timeout); this.timeout = undefined; } }; + componentWillUmount() { this.clearTimeout(); } + load_error = () => { if (this.state.attempts < 5) { this.clearTimeout(); @@ -42,6 +46,7 @@ export class IFrame extends Component { ); } }; + render_iframe = () => { const src = get_blob_url(this.props.project_id, "html", this.props.sha1) + @@ -57,6 +62,7 @@ export class IFrame extends Component { /> ); }; + render() { if (this.state.show) { return this.render_iframe(); diff --git a/src/smc-webapp/jupyter/cell-output-message.tsx b/src/smc-webapp/jupyter/cell-output-message.tsx index d6197b172a..4281b946df 100644 --- a/src/smc-webapp/jupyter/cell-output-message.tsx +++ b/src/smc-webapp/jupyter/cell-output-message.tsx @@ -1,13 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__ - * DS104: Avoid inline assignments - * DS202: Simplify dynamic range loops - * DS205: Consider reworking code to avoid use of IIFEs - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ /* Handling of output messages. @@ -16,17 +6,21 @@ TODO: most components should instead be in separate files. declare const $: any; -import { React, Component } from "../app-framework"; // TODO: this will move +const { Markdown, HTML } = require("../r_misc"); +const Ansi = require("ansi-to-react"); + +import { React, Component, Rendered } from "smc-webapp/app-framework"; import { Button } from "react-bootstrap"; import * as immutable from "immutable"; -const misc = require("smc-util/misc"); -const { Icon, Markdown, HTML } = require("../r_misc"); -// const { sanitize_html } = require("../misc_page"); -const Ansi = require("ansi-to-react"); -const { IFrame } = require("./cell-output-iframe"); -const { get_blob_url } = require("./server-urls"); -const { javascript_eval } = require("./javascript-eval"); -const { is_redux, is_redux_actions } = require("../app-framework"); +import { Icon } from "../r_misc/icon"; +import { IFrame } from "./cell-output-iframe"; +import { get_blob_url } from "./server-urls"; +import { javascript_eval } from "./javascript-eval"; + +import { delay } from "awaiting"; +import { JupyterActions } from "./actions"; + +import { endswith, is_array, merge } from "smc-util/misc2"; const OUT_STYLE: React.CSSProperties = { whiteSpace: "pre-wrap", @@ -39,25 +33,25 @@ const OUT_STYLE: React.CSSProperties = { // const ANSI_STYLE: React.CSSProperties = OUT_STYLE; const STDOUT_STYLE: React.CSSProperties = OUT_STYLE; -const STDERR_STYLE: React.CSSProperties = misc.merge( +const STDERR_STYLE: React.CSSProperties = merge( { backgroundColor: "#fdd" }, STDOUT_STYLE ); -const TRACEBACK_STYLE: React.CSSProperties = misc.merge( +const TRACEBACK_STYLE: React.CSSProperties = merge( { backgroundColor: "#f9f2f4" }, OUT_STYLE ); interface StdoutProps { - message: immutable.Map; + message: immutable.Map; } export class Stdout extends Component { - shouldComponentUpdate(nextProps) { - return !immutable_equals(this.props, nextProps); + shouldComponentUpdate(nextProps: StdoutProps): boolean { + return !this.props.message.equals(nextProps.message); } - render() { + render(): Rendered { const value = this.props.message.get("text"); if (is_ansi(value)) { return ( @@ -77,14 +71,15 @@ export class Stdout extends Component { } interface StderrProps { - message: immutable.Map; + message: immutable.Map; } export class Stderr extends Component { - shouldComponentUpdate(nextProps) { - return !immutable_equals(this.props, nextProps); + shouldComponentUpdate(nextProps: StderrProps): boolean { + return !this.props.message.equals(nextProps.message); } - render() { + + render(): Rendered { const value = this.props.message.get("text"); if (is_ansi(value)) { return ( @@ -93,7 +88,7 @@ export class Stderr extends Component { ); } - // span below? what? -- See https://github.com/sagemathinc/cocalc/issues/1958 + // span -- see https://github.com/sagemathinc/cocalc/issues/1958 return (
{value} @@ -116,39 +111,36 @@ interface ImageState { } class Image extends Component { - private _is_mounted: any; // TODO: dont do this + private is_mounted: any; // TODO: dont do this constructor(props: ImageProps, context: any) { super(props, context); this.state = { attempts: 0 }; } - load_error = () => { - if (this.state.attempts < 5 && this._is_mounted) { - const f = () => { - if (this._is_mounted) { - return this.setState({ attempts: this.state.attempts + 1 }); - } - }; - return setTimeout(f, 500); + load_error = async (): Promise => { + if (this.state.attempts < 5 && this.is_mounted) { + await delay(500); + if (!this.is_mounted) return; + this.setState({ attempts: this.state.attempts + 1 }); } }; - componentDidMount() { - return (this._is_mounted = true); + componentDidMount(): void { + this.is_mounted = true; } - componentWillUnmount() { - return (this._is_mounted = false); + componentWillUnmount(): void { + this.is_mounted = false; } - extension = () => { + extension = (): string => { return this.props.type.split("/")[1].split("+")[0]; }; - render_using_server() { + render_using_server(project_id: string, sha1: string): Rendered { const src = - get_blob_url(this.props.project_id, this.extension(), this.props.sha1) + + get_blob_url(project_id, this.extension(), sha1) + `&attempts=${this.state.attempts}`; return ( { ); } - encoding = () => { + encoding = (): string => { switch (this.props.type) { case "image/svg+xml": return "utf8"; @@ -169,27 +161,23 @@ class Image extends Component { } }; - render_locally() { - if (this.props.value == null) { - // should never happen - return ; - } + render_locally(value: string): Rendered { // The encodeURIComponent is definitely necessary these days. // See https://github.com/sagemathinc/cocalc/issues/3197 and the comments at // https://css-tricks.com/probably-dont-base64-svg/ const src = `data:${ this.props.type - };${this.encoding()},${encodeURIComponent(this.props.value)}`; + };${this.encoding()},${encodeURIComponent(value)}`; return ( ); } - render() { + render(): Rendered { if (this.props.value != null) { - return this.render_locally(); + return this.render_locally(this.props.value); } else if (this.props.sha1 != null && this.props.project_id != null) { - return this.render_using_server(); + return this.render_using_server(this.props.project_id, this.props.sha1); } else { // not enough info to render return [unavailable {this.extension()} image]; @@ -203,7 +191,7 @@ interface TextPlainProps { class TextPlain extends Component { render() { - // span? what? -- See https://github.com/sagemathinc/cocalc/issues/1958 + // span? -- see https://github.com/sagemathinc/cocalc/issues/1958 return (
{this.props.value} @@ -225,46 +213,59 @@ class UntrustedJavascript extends Component { } interface JavascriptProps { - value: any | string; // TODO: not used? + value: string | immutable.List; } class Javascript extends Component { private node: HTMLElement; - componentDidMount() { + + componentDidMount(): void { const element = $(this.node); element.empty(); - let { value } = this.props; - if (typeof value !== "string") { - value = value.toJS(); + let value: string[]; + if (typeof this.props.value == "string") { + value = [this.props.value]; + } else { + const x = this.props.value.toJS(); + if (!is_array(x)) { + console.warn("not evaluating javascript since wrong type:", x); + return; + } else { + value = x; + } } - if (!misc.is_array(value)) { - value = [value]; + let block: string; + for (block of value) { + javascript_eval(block, element); } - return value.map(line => javascript_eval(line, element)); } - render() { - return
(this.node = node)} />; + render(): Rendered { + return
; } } interface PDFProps { - project_id?: string; - value: any | string; + project_id: string; + value: string | immutable.Map; } class PDF extends Component { - render() { - let href; - if (misc.is_string(this.props.value)) { + render(): Rendered { + let href: string; + if (typeof this.props.value == "string") { href = get_blob_url(this.props.project_id, "pdf", this.props.value); } else { - const value = this.props.value.get("value"); - href = `data:application/pdf;base64,${value}`; + href = `data:application/pdf;base64,${this.props.value.get("value")}`; } return ( @@ -273,19 +274,24 @@ class PDF extends Component { } interface DataProps { - message: immutable.Map; + message: immutable.Map; project_id?: string; directory?: string; id?: string; - actions?: any; + actions?: JupyterActions; trust?: boolean; } class Data extends Component { - shouldComponentUpdate(nextProps) { - return !immutable_equals(this.props, nextProps); + shouldComponentUpdate(nextProps): boolean { + return ( + !this.props.message.equals(nextProps.message) || + this.props.id != nextProps.id || + this.props.trust != nextProps.trust + ); } - render_html(value: any) { + + render_html(value: string): Rendered { return (
{
); } - render_markdown(value: any) { + + render_markdown(value: string): Rendered { return (
{
); } - render() { - let type: any = undefined; - let value: any = undefined; + + render(): Rendered { const data = this.props.message.get("data"); - __guardMethod__(data, "forEach", o => - o.forEach(function(v, k) { - type = k; - value = v; - return false; - }) - ); - if (type) { + if (data == null || typeof data.forEach != "function") return; + + let type: string = ""; + let value: any = undefined; + data.forEach(function(v, k) { + type = k; + value = v; + return false; + }); + + if (type != "") { const [a, b] = type.split("/"); switch (a) { case "text": @@ -336,13 +345,16 @@ class Data extends Component { ); } return ; + case "html": case "latex": // put latex as HTML, since jupyter requires $'s anyways. return this.render_html(value); + case "markdown": return this.render_markdown(value); } break; + case "image": let height: any; let width: any; @@ -376,8 +388,10 @@ class Data extends Component { height={height} /> ); + case "iframe": return