diff --git a/packages/app-settings/src/Developer.tsx b/packages/app-settings/src/Developer.tsx new file mode 100644 index 0000000000..3ff3722eb8 --- /dev/null +++ b/packages/app-settings/src/Developer.tsx @@ -0,0 +1,185 @@ +// Copyright 2017-2019 @polkadot/app-settings 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 { AppProps, I18nProps } from '@polkadot/ui-app/types'; + +import React from 'react'; +import store from 'store'; +import { getTypeRegistry } from '@polkadot/types'; +import { Button, Editor, InputFile } from '@polkadot/ui-app/index'; +import { ActionStatus } from '@polkadot/ui-app/Status/types'; +import { stringToU8a, u8aToString } from '@polkadot/util'; + +import translate from './translate'; + +type Props = AppProps & I18nProps & { + onStatusChange: (status: ActionStatus) => void +}; + +type State = { + code: string, + isJsonValid: boolean, + isTypesValid: boolean, + types?: { [index: string]: any } | null, + typesPlaceholder?: string +}; + +class Developer extends React.PureComponent { + private defaultCode: string = `{\n\n}`; + + constructor (props: Props) { + super(props); + + const types = store.get('types') || {}; + const names = Object.keys(types); + + this.state = { + code: Object.keys(types).length ? JSON.stringify(types, null, 2) : this.defaultCode, + isJsonValid: true, + isTypesValid: true, + types: names.length ? types : null, + typesPlaceholder: names.length + ? names.join(', ') + : undefined + }; + } + + render () { + const { t } = this.props; + const { code, isJsonValid, isTypesValid, types, typesPlaceholder } = this.state; + + return ( +
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+ +
+ ); + } + + private clearTypes = (): void => { + store.remove('types'); + this.setState({ + code: this.defaultCode, + isJsonValid: true, + isTypesValid: true, + types: null, + typesPlaceholder: undefined + }); + } + + private onChangeTypes = (data: Uint8Array) => { + + const dataToString = u8aToString(data); + + try { + // @TODO: Include isJsonObject function from https://github.com/polkadot-js/common/pull/359 + const types = JSON.parse(dataToString); + const typesPlaceholder = Object.keys(types).join(', '); + + console.log('Registering types:', typesPlaceholder); + + getTypeRegistry().register(types); + + this.setState({ + code: dataToString, + isJsonValid: true, + isTypesValid: true, + types, + typesPlaceholder + }); + + } catch (error) { + + console.error('Error registering types:', error); + + this.setState({ + code: dataToString, + isJsonValid: false, + isTypesValid: false, + types: null, + typesPlaceholder: error.message + }); + } + } + + private onEditTypes = (code: string): void => { + try { + // @TODO: Include isJsonObject function from https://github.com/polkadot-js/common/pull/359 + JSON.parse(code); + + this.setState({ + code, + isJsonValid: true + } as State); + + this.onChangeTypes(stringToU8a(code)); + } catch (e) { + this.setState({ + code, + isJsonValid: false, + typesPlaceholder: e.message + } as State); + } + } + + private saveDeveloper = (): void => { + const { t } = this.props; + const { isTypesValid, types } = this.state; + + const status = { + status: 'success', + action: t('Your custom types have been added') + } as ActionStatus; + + if (isTypesValid) { + store.set('types', types); + } else { + status.status = 'error'; + status.action = t('Your custom types are invalid'); + } + + this.props.onStatusChange(status); + } +} + +export default translate(Developer); diff --git a/packages/app-settings/src/General.tsx b/packages/app-settings/src/General.tsx new file mode 100644 index 0000000000..5fda89321a --- /dev/null +++ b/packages/app-settings/src/General.tsx @@ -0,0 +1,201 @@ +// Copyright 2017-2019 @polkadot/app-settings 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 { AppProps, I18nProps } from '@polkadot/ui-app/types'; +import { SettingsStruct } from '@polkadot/ui-settings/types'; + +import React from 'react'; +import { Tab } from 'semantic-ui-react'; +import { Button, Dropdown, Input } from '@polkadot/ui-app/index'; +import { ActionStatus } from '@polkadot/ui-app/Status/types'; +import uiSettings from '@polkadot/ui-settings'; + +import './index.css'; + +import translate from './translate'; + +type Props = AppProps & I18nProps & { + onStatusChange: (status: ActionStatus) => void +}; + +type State = { + isCustomNode: boolean, + isUrlValid: boolean, + settings: SettingsStruct +}; + +class General extends React.PureComponent { + constructor (props: Props) { + super(props); + + const settings = uiSettings.get(); + + let isCustomNode = true; + + // check to see if user has saved a custom node by seeing if their URL is equal to any preset + for (let i = 0; i < uiSettings.availableNodes.length; i++) { + if (uiSettings.availableNodes[i].value === settings.apiUrl) { + isCustomNode = false; + } + } + + this.state = { + isCustomNode, + isUrlValid: this.isValidUrl(settings.apiUrl), + settings + }; + } + + render () { + const { t } = this.props; + const { isUrlValid, settings: { i18nLang, uiMode, uiTheme } } = this.state; + + return ( +
+
+ {this.renderEndpoint()} +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+ ); + } + + private renderEndpoint = () => { + const { t } = this.props; + const { isUrlValid, settings: { apiUrl } } = this.state; + + const preset = ( + + ); + + const custom = ( + + ); + + const panes = [ + { menuItem: t('pre-set'), render: () => {preset} }, + { menuItem: t('custom'), render: () => {custom} } + ]; + + return ( + + ); + } + + private onChangeApiUrl = (apiUrl: string): void => { + this.setState(({ settings }: State) => ({ + isUrlValid: this.isValidUrl(apiUrl), + settings: { + ...settings, + apiUrl + } + })); + } + + private onChangeLang = (i18nLang: string): void => { + // ignore (for now), here to future-proof + } + + private onChangeUiMode = (uiMode: string): void => { + this.setState(({ settings }: State) => ({ + settings: { + ...settings, + uiMode + } + })); + } + + private onChangeUiTheme = (uiTheme: string): void => { + this.setState(({ settings }: State) => ({ + settings: { + ...settings, + uiTheme + } + })); + } + + private toggleCustomNode = (): void => { + this.setState(({ isCustomNode, settings }: State) => { + // reset URL to a preset when toggled to preset + const apiUrl = isCustomNode + ? uiSettings.availableNodes[0].value + : settings.apiUrl; + + return { + isCustomNode: !isCustomNode, + isUrlValid: true, + settings: { + ...settings, + apiUrl + } + }; + }); + } + + private isValidUrl (apiUrl: string): boolean { + return ( + // some random length... we probably want to parse via some lib + (apiUrl.length >= 7) && + // check that it starts with a valid ws identifier + (apiUrl.startsWith('ws://') || apiUrl.startsWith('wss://')) + ); + } + private saveAndReload = (): void => { + const { settings } = this.state; + + uiSettings.set(settings); + + // HACK This is terribe, but since the API needs to re-connect, but since + // the API does not yet handle re-connections properly, it is what it is + window.location.reload(); + } +} + +export default translate(General); diff --git a/packages/app-settings/src/index.css b/packages/app-settings/src/index.css index 68f56b1f21..6c8ae06962 100644 --- a/packages/app-settings/src/index.css +++ b/packages/app-settings/src/index.css @@ -2,8 +2,32 @@ /* This software may be modified and distributed under the terms /* of the Apache-2.0 license. See the LICENSE file for details. */ -.sub-label { - cursor: pointer; - padding: 0rem .5833rem; - text-align: right; +.settings-General { + + .ui.menu { + justify-content: flex-end; + margin-bottom: 0; + + .active.item { + font-weight: bold; + } + } + + .ui.attached.segment.tab { + padding: 0; + border: none; + } + + .sub-label { + cursor: pointer; + padding: 0rem .5833rem; + text-align: right; + } } + +.settings-Developer { + .editor { + height: 300px; + position: relative; + } +} \ No newline at end of file diff --git a/packages/app-settings/src/index.tsx b/packages/app-settings/src/index.tsx index 8021f5bfbd..c759d371e8 100644 --- a/packages/app-settings/src/index.tsx +++ b/packages/app-settings/src/index.tsx @@ -4,30 +4,21 @@ import { AppProps, I18nProps } from '@polkadot/ui-app/types'; import { TabItem } from '@polkadot/ui-app/Tabs'; -import { SettingsStruct } from '@polkadot/ui-settings/types'; import React from 'react'; import { Route, Switch } from 'react-router'; -import store from 'store'; -import { getTypeRegistry } from '@polkadot/types'; -import { Button, Dropdown, Input, InputFile, Tabs } from '@polkadot/ui-app/index'; -import uiSettings from '@polkadot/ui-settings'; -import { u8aToString } from '@polkadot/util'; +import { Tabs } from '@polkadot/ui-app/index'; import './index.css'; import translate from './translate'; +import Developer from './Developer'; +import General from './General'; type Props = AppProps & I18nProps; type State = { - isCustomNode: boolean, - isTypesValid: boolean, - isUrlValid: boolean, - settings: SettingsStruct, - tabs: Array, - types?: { [index: string]: any } | null, - typesPlaceholder?: string + tabs: Array }; class App extends React.PureComponent { @@ -35,22 +26,8 @@ class App extends React.PureComponent { super(props); const { t } = props; - const types = store.get('types') || {}; - const names = Object.keys(types); - const settings = uiSettings.get(); - let isCustomNode = true; - - // check to see if user has saved a custom node by seeing if their URL is equal to any preset - for (let i = 0; i < uiSettings.availableNodes.length; i++) { - if (uiSettings.availableNodes[i].value === settings.apiUrl) { - isCustomNode = false; - } - } this.state = { - isCustomNode, - isTypesValid: true, - isUrlValid: this.isValidUrl(settings.apiUrl), tabs: [ { name: 'general', @@ -60,11 +37,7 @@ class App extends React.PureComponent { name: 'developer', text: t('Developer') } - ], - typesPlaceholder: names.length - ? names.join(', ') - : undefined, - settings + ] }; } @@ -81,231 +54,21 @@ class App extends React.PureComponent { /> - - + + ); } - private renderDeveloper = () => { - const { t } = this.props; - const { isTypesValid, types, typesPlaceholder } = this.state; - + private DeveloperWithStatus = () => { return ( - <> -
-
- -
-
- -