diff --git a/packages/app-js/src/Editor.tsx b/packages/app-js/src/Editor.tsx index 6727b757a8..fd04b7f72b 100644 --- a/packages/app-js/src/Editor.tsx +++ b/packages/app-js/src/Editor.tsx @@ -1,4 +1,4 @@ -// Copyright 2017-2019 @polkadot/app-123code authors & contributors +// Copyright 2017-2019 @polkadot/app-js authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. @@ -7,74 +7,79 @@ import { BareProps } from '@polkadot/ui-app/types'; import React from 'react'; import CodeFlask from 'codeflask'; -import DEFAULT_CODE from './snippets/newHead'; import WRAPPING from './snippets/wrapping'; type Props = BareProps & { children?: React.ReactNode, - onEdit: (code: string) => void -}; -type State = { - code: string + code: string, + onEdit: (code: string) => void, + snippet: string }; -export default class Editor extends React.PureComponent { - private id: string; +type State = { + snippet: string +}; + +export default class Editor extends React.PureComponent { + private id: string = `flask-${Date.now()}`; + private editor: any; + + state: State = { + snippet: '' + }; constructor (props: Props) { super(props); - this.id = `flask-${Date.now()}`; - this.state = { - code: `${WRAPPING}${DEFAULT_CODE}` - }; + } + + static getDerivedStateFromProps (nextProps: Props, prevState: State) { + if (nextProps.snippet !== prevState.snippet) { + return { + snippet: nextProps.snippet + }; + } + return null; } componentDidMount () { - const { onEdit } = this.props; - const { code } = this.state; - - const editor = new CodeFlask(`#${this.id}`, { + this.editor = new CodeFlask(`#${this.id}`, { language: 'js', lineNumbers: true }); + const { editor, props: { code, onEdit } } = this; - editor.updateCode(code); + editor.updateCode(`${WRAPPING}${code}`); editor.onUpdate((code: string) => { - this.onEdit(code); + onEdit(code); }); editor.editorRoot.addEventListener('focusin', () => { - editor.onUpdate(this.onEdit); - }); - - editor.editorRoot.addEventListener('focusout', () => { - editor.onUpdate(() => { - // empty - }); + this.editor.onUpdate(onEdit); }); onEdit(code); } - render () { - const { children } = this.props; + componentDidUpdate () { + const { code, onEdit, snippet } = this.props; + if (snippet !== this.state.snippet) { + onEdit(code); + this.editor.updateCode(`${WRAPPING}${code}`); + } + } + + render () { return ( -
+
- {children} + {this.props.children}
); } - - private onEdit = (code: string): void => { - const { onEdit } = this.props; - - this.setState({ code }, () => onEdit(code)); - } } diff --git a/packages/app-js/src/Intro.tsx b/packages/app-js/src/Intro.tsx new file mode 100644 index 0000000000..43aef7afba --- /dev/null +++ b/packages/app-js/src/Intro.tsx @@ -0,0 +1,27 @@ +// Copyright 2017-2019 @polkadot/app-js authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { I18nProps } from '@polkadot/ui-app/types'; + +import { Trans } from 'react-i18next'; +import React from 'react'; + +import translate from './translate'; + +type Props = I18nProps; + +const Intro = (props: Props) => { + const { t } = props; + return ( + +

All code is wrapped within an async closure, allowing access to + @polkadot/api, + @polkadot/util-crypto, + @polkadot/ui-keyring and + @polkadot/util. +

+
+ ); +}; +export default translate(Intro); diff --git a/packages/app-js/src/Output.tsx b/packages/app-js/src/Output.tsx index 91c3241ce8..6bcce5c2d3 100644 --- a/packages/app-js/src/Output.tsx +++ b/packages/app-js/src/Output.tsx @@ -1,4 +1,4 @@ -// Copyright 2017-2019 @polkadot/app-123code authors & contributors +// Copyright 2017-2019 @polkadot/app-js authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. @@ -6,33 +6,46 @@ import { BareProps } from '@polkadot/ui-app/types'; import { Log } from './types'; import React from 'react'; -import { format } from '@polkadot/util/logger'; +import { isError, isNull, isUndefined } from '@polkadot/util'; type Props = BareProps & { children?: React.ReactNode, logs: Array }; -export default class Output extends React.PureComponent { - render () { - const { children, logs } = this.props; - - return ( -
- {logs.map(this.renderEntry)} - {children} -
- ); +const format = (value: any): string => { + if (isError(value)) { + return value.stack ? value.stack : value.toString(); + } else if (isUndefined(value)) { + return 'undefined'; + } else if (isNull(value)) { + return 'null'; + } else if (Array.isArray(value)) { + return `[${value.map((value) => format(value)).join(', ')}]`; + } else if (value instanceof Map) { + return `{${[...value.entries()].map(([key, value]) => key + ': ' + format(value)).join(', ')}}`; } - private renderEntry = ({ args, type }: Log, index: number) => { - return ( -
- {args.map((arg) => format(arg)).join(' ')} + return value.toString(); +}; + +const renderEntry = ({ args, type }: Log, index: number): React.ReactNode => ( +
+ {args.map((arg) => format(arg)).join(' ')} +
+); + +export default (props: Props) => { + return ( +
+
+
+
+ {props.logs.map(renderEntry)} +
+
- ); - } -} + {props.children} +
+ ); +}; diff --git a/packages/app-js/src/codeflask.d.ts b/packages/app-js/src/codeflask.d.ts index 11a82f1f3c..b9928ab0ca 100644 --- a/packages/app-js/src/codeflask.d.ts +++ b/packages/app-js/src/codeflask.d.ts @@ -1,4 +1,4 @@ -// Copyright 2017-2019 @polkadot/app-123code authors & contributors +// Copyright 2017-2019 @polkadot/app-js authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. diff --git a/packages/app-js/src/index.css b/packages/app-js/src/index.css index fd865d50e6..7b3bf7e6b9 100644 --- a/packages/app-js/src/index.css +++ b/packages/app-js/src/index.css @@ -3,15 +3,35 @@ /* of the Apache-2.0 license. See the LICENSE file for details. */ .js--App { + display: flex; + flex-direction: column; + height: 100vh; padding: 1rem 1.5rem; +} - /* .container { - padding: 0.25rem; - } */ +.js--Content { + align-items: stretch; + display: flex; + flex-grow: 2; + justify-content: space-between; + height: 100%; +} + +.js--Dropdown { + margin-right: 100px; + position: relative; + z-index: 200; + .dropdown .menu > .item { + display: flex; + flex-direction: row-reverse; + justify-content: space-between; + } } .js--Editor, .js--Output { + min-width: 200px; + .action-button { position: absolute; right: 0.25rem; @@ -21,12 +41,46 @@ } .js--Output { - margin-top: 1rem; - padding: 10px; + background-color: #4e4e4e; + color: #ffffff; + display: flex; + flex-direction: column; font-family: monospace; - font-size: 14px; + font-size: 13px; font-variant-ligatures: common-ligatures; + overflow: auto; + padding: 10px; position: relative; + resize: horizontal; + width: 50%; + + &::after { + font-size: 24px; + line-height: 24px; + content: '↔'; + cursor: col-resize; + z-index: 1; + width: 22px; + height: 30px; + position: absolute; + bottom: 0; + right: 0; + } + + .logs-wrapper { + display: flex; + flex: 1; + min-height: 0px; + } + + .logs-container { + flex: 1; + overflow: auto;; + } + + .logs-content { + height: auto; + } .js--Log { animation: fadein 0.2s; @@ -34,26 +88,25 @@ word-break: break-all; &.error { - color: red; + color: #f88; } } } .js--Editor { + flex-grow: 2; padding: 0; position: relative; - > div.container { - width: 100%; - height: 60vh; - position: relative; - } - - .codeflask { - /* background: #f4f7fb; */ - } - textarea { outline: 0; } + .codeflask--has-line-numbers .codeflask__flatten { + min-width: calc(100% - 40px); + width: auto; + } + .codeflask__lines { + background: #eee; + z-index: 100; + } } diff --git a/packages/app-js/src/index.tsx b/packages/app-js/src/index.tsx index afa6793f24..d827e85246 100644 --- a/packages/app-js/src/index.tsx +++ b/packages/app-js/src/index.tsx @@ -2,58 +2,108 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { ApiPromise } from '@polkadot/api'; +import { KeyringInstance } from '@polkadot/keyring/types'; import { ApiProps } from '@polkadot/ui-api/types'; import { AppProps, I18nProps } from '@polkadot/ui-app/types'; import { Log, LogType } from './types'; import React from 'react'; import { withApi, withMulti } from '@polkadot/ui-api/index'; -import { Button } from '@polkadot/ui-app/index'; +import { Button, Dropdown } from '@polkadot/ui-app/index'; import uiKeyring from '@polkadot/ui-keyring'; import * as util from '@polkadot/util'; import * as hashing from '@polkadot/util-crypto'; import './index.css'; - -import Editor from './Editor'; -import Output from './Output'; +import snippets from './snippets'; import translate from './translate'; +import Intro from './Intro'; +import Editor from './Editor'; +import Output from './Output'; + +type Injected = { + api: ApiPromise, + console: { + error: (...args: Array) => void, + log: (...args: Array) => void + }, + global: null, + hashing: typeof hashing, + keyring: KeyringInstance, + util: typeof util, + window: null +}; type Props = ApiProps & AppProps & I18nProps; type State = { code: string, - logs: Array + isRunning: boolean, + logs: Array, + snippet: string }; +const local = snippets.find(obj => obj.value === localStorage.getItem('app-js-snippet')); + class App extends React.PureComponent { + injected: Injected | null = null; state: State = { - code: '', - logs: [] + code: local ? local.code : snippets[0].code, + isRunning: false, + logs: [], + snippet: local ? local.value : snippets[0].value }; render () { - const { logs } = this.state; + const { code, isRunning, logs, snippet } = this.state; + const { t } = this.props; + const options = snippets.map(({ code, ...options }) => ({ ...options })); return (
- -
+ + +