Developer.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright 2017-2020 @polkadot/app-settings authors & contributors
  2. // This software may be modified and distributed under the terms
  3. // of the Apache-2.0 license. See the LICENSE file for details.
  4. import { AppProps as Props } from '@polkadot/react-components/types';
  5. import React, { useEffect, useState } from 'react';
  6. import { Trans } from 'react-i18next';
  7. import store from 'store';
  8. import styled from 'styled-components';
  9. import { registry } from '@polkadot/react-api';
  10. import { Button, Editor, InputFile } from '@polkadot/react-components';
  11. import { isJsonObject, stringToU8a, u8aToString } from '@polkadot/util';
  12. import { useTranslation } from './translate';
  13. const EMPTY_CODE = '{\n\n}';
  14. const EMPTY_TYPES = {};
  15. function Developer ({ className, onStatusChange }: Props): React.ReactElement<Props> {
  16. const { t } = useTranslation();
  17. const [code, setCode] = useState(EMPTY_CODE);
  18. const [isJsonValid, setIsJsonValid] = useState(true);
  19. const [isTypesValid, setIsTypesValid] = useState(true);
  20. const [types, setTypes] = useState<Record<string, any>>(EMPTY_TYPES);
  21. const [typesPlaceholder, setTypesPlaceholder] = useState<string | null>(null);
  22. useEffect((): void => {
  23. const types = store.get('types') || {};
  24. if (Object.keys(types).length) {
  25. setCode(JSON.stringify(types, null, 2));
  26. setTypes({});
  27. setTypesPlaceholder(Object.keys(types).join(', '));
  28. }
  29. }, []);
  30. const _setState = ({ code, isJsonValid, isTypesValid, types, typesPlaceholder }: { code: string; isJsonValid: boolean; isTypesValid: boolean; types: Record<string, any>; typesPlaceholder: string | null }): void => {
  31. setCode(code);
  32. setIsJsonValid(isJsonValid);
  33. setIsTypesValid(isTypesValid);
  34. setTypes(types);
  35. setTypesPlaceholder(typesPlaceholder);
  36. };
  37. const _clearTypes = (): void => {
  38. store.remove('types');
  39. _setState({
  40. code: EMPTY_CODE,
  41. isJsonValid: true,
  42. isTypesValid: true,
  43. types: EMPTY_TYPES,
  44. typesPlaceholder: null
  45. });
  46. };
  47. const _onChangeTypes = (data: Uint8Array): void => {
  48. const code = u8aToString(data);
  49. try {
  50. const types = JSON.parse(code);
  51. const typesPlaceholder = Object.keys(types).join(', ');
  52. console.log('Detected types:', typesPlaceholder);
  53. _setState({
  54. code,
  55. isJsonValid: true,
  56. isTypesValid: true,
  57. types: Object.keys(types).length === 0 ? {} : types,
  58. typesPlaceholder
  59. });
  60. } catch (error) {
  61. console.error('Error registering types:', error);
  62. _setState({
  63. code,
  64. isJsonValid: false,
  65. isTypesValid: false,
  66. types: {},
  67. typesPlaceholder: error.message
  68. });
  69. }
  70. };
  71. const _onEditTypes = (code: string): void => {
  72. try {
  73. if (!isJsonObject(code)) {
  74. throw Error(t('This is not a valid JSON object.'));
  75. }
  76. _onChangeTypes(stringToU8a(code));
  77. } catch (e) {
  78. setCode(code);
  79. setIsJsonValid(false);
  80. setTypesPlaceholder(e.message);
  81. }
  82. };
  83. const _saveDeveloper = (): void => {
  84. try {
  85. registry.register(types);
  86. store.set('types', types);
  87. setIsTypesValid(true);
  88. onStatusChange({
  89. status: 'success',
  90. action: t('Your custom types have been added')
  91. });
  92. } catch (error) {
  93. console.error(error);
  94. setIsTypesValid(false);
  95. onStatusChange({
  96. status: 'error',
  97. action: t(`Error saving your custom types. ${error.message}`)
  98. });
  99. }
  100. };
  101. const typesHasNoEntries = Object.keys(types).length === 0;
  102. // Trans component
  103. /* eslint-disable react/jsx-max-props-per-line */
  104. return (
  105. <div className={className}>
  106. <div className='ui--row'>
  107. <div className='full'>
  108. <InputFile
  109. clearContent={typesHasNoEntries && isTypesValid}
  110. help={t('Save the type definitions for your custom structures as key-value pairs in a valid JSON file. The key should be the name of your custom structure and the value an object containing your type definitions.')}
  111. isError={!isTypesValid}
  112. label={t('Additional types as a JSON file (or edit below)')}
  113. onChange={_onChangeTypes}
  114. placeholder={typesPlaceholder}
  115. />
  116. </div>
  117. </div>
  118. <div className='ui--row'>
  119. <div className='full'>
  120. <Editor
  121. className='editor'
  122. code={code}
  123. isValid={isJsonValid}
  124. onEdit={_onEditTypes}
  125. />
  126. </div>
  127. </div>
  128. <div className='ui--row'>
  129. <div className='full'>
  130. <Trans i18nKey='devConfig'><div className='help'>If you are a development team with at least a test network available, consider adding the types directly <a href='https://github.com/polkadot-js/apps/tree/master/packages/apps-config' rel='noopener noreferrer' target='_blank'>to the apps-config</a>, allowing out of the box operation for your spec &amp; chains, both for you and anybody trying to connect to it. This is not a replacement for your chain-specific UI, however doing so does help in allowing users to easily discover and use with zero-config.</div></Trans>
  131. </div>
  132. </div>
  133. <Button.Group>
  134. <Button
  135. icon='sync'
  136. label={t('Reset')}
  137. onClick={_clearTypes}
  138. />
  139. <Button.Or />
  140. <Button
  141. icon='save'
  142. isDisabled={!isTypesValid || !isJsonValid}
  143. label={t('Save')}
  144. onClick={_saveDeveloper}
  145. />
  146. </Button.Group>
  147. </div>
  148. );
  149. }
  150. export default React.memo(styled(Developer)`
  151. .editor {
  152. height: 21rem;
  153. margin-left: 2rem;
  154. position: relative;
  155. }
  156. .help {
  157. padding: 0.5rem 2rem;
  158. }
  159. `);