General.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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 { Option } from '@polkadot/apps-config/settings/types';
  5. import React, { useCallback, useEffect, useState, useMemo } from 'react';
  6. import { createLanguages, createSs58 } from '@polkadot/apps-config/settings';
  7. import { isLedgerCapable } from '@polkadot/react-api';
  8. import { Button, ButtonCancel, Dropdown, Modal } from '@polkadot/react-components';
  9. import { settings as uiSettings } from '@polkadot/ui-settings';
  10. import { SettingsStruct } from '@polkadot/ui-settings/types';
  11. import { useTranslation } from './translate';
  12. import { createIdenticon, createOption, save, saveAndReload } from './util';
  13. import SelectUrl from './SelectUrl';
  14. interface Props {
  15. className?: string;
  16. isModalContent?: boolean;
  17. onClose: () => void;
  18. }
  19. const ledgerConnOptions = uiSettings.availableLedgerConn;
  20. function General ({ className = '', isModalContent, onClose }: Props): React.ReactElement<Props> {
  21. const { t } = useTranslation();
  22. // tri-state: null = nothing changed, false = no reload, true = reload required
  23. const [changed, setChanged] = useState<boolean | null>(null);
  24. const [settings, setSettings] = useState(uiSettings.get());
  25. const iconOptions = useMemo(
  26. () => uiSettings.availableIcons.map((o): Option => createIdenticon(o, ['default'])),
  27. []
  28. );
  29. const prefixOptions = useMemo(
  30. () => createSs58(t).map((o): Option | React.ReactNode => createOption(o, ['default'])),
  31. [t]
  32. );
  33. const translateLanguages = useMemo(
  34. () => createLanguages(t),
  35. [t]
  36. );
  37. useEffect((): void => {
  38. const prev = uiSettings.get() as unknown as Record<string, unknown>;
  39. const hasChanges = Object.entries(settings).some(([key, value]) => prev[key] !== value);
  40. const needsReload = prev.apiUrl !== settings.apiUrl || prev.prefix !== settings.prefix;
  41. setChanged(
  42. hasChanges
  43. ? needsReload
  44. : null
  45. );
  46. }, [settings]);
  47. const _handleChange = useCallback(
  48. (key: keyof SettingsStruct) => <T extends string | number>(value: T): void =>
  49. setSettings((settings) => ({ ...settings, [key]: value })),
  50. []
  51. );
  52. const _saveAndReload = useCallback(
  53. (): void => saveAndReload(settings),
  54. [settings]
  55. );
  56. const _save = useCallback(
  57. (): void => {
  58. save(settings);
  59. setChanged(null);
  60. },
  61. [settings]
  62. );
  63. const { i18nLang, icon, ledgerConn, prefix, uiMode } = settings;
  64. const networkSelector = <SelectUrl onChange={_handleChange('apiUrl')} />;
  65. return (
  66. <div className={className}>
  67. {isModalContent
  68. ? (
  69. <Modal.Columns>
  70. <Modal.Column>{networkSelector}</Modal.Column>
  71. <Modal.Column>
  72. {t<string>('The RPC node can be selected from the pre-defined list or manually entered, depending on the chain you wish to connect to.')}
  73. </Modal.Column>
  74. </Modal.Columns>
  75. )
  76. : networkSelector
  77. }
  78. {!isModalContent && (
  79. <>
  80. <div className='ui--row'>
  81. <Dropdown
  82. defaultValue={prefix}
  83. help={t<string>('Override the default ss58 prefix for address generation')}
  84. label={t<string>('address prefix')}
  85. onChange={_handleChange('prefix')}
  86. options={prefixOptions}
  87. />
  88. </div>
  89. <div className='ui--row'>
  90. <Dropdown
  91. defaultValue={icon}
  92. help={t<string>('Override the default identity icon display with a specific theme')}
  93. label={t<string>('default icon theme')}
  94. onChange={_handleChange('icon')}
  95. options={iconOptions}
  96. />
  97. </div>
  98. <div className='ui--row'>
  99. <Dropdown
  100. defaultValue={uiMode}
  101. help={t<string>('Adjust the mode from basic (with a limited number of beginner-user-friendly apps) to full (with all basic & advanced apps available)')}
  102. label={t<string>('interface operation mode')}
  103. onChange={_handleChange('uiMode')}
  104. options={uiSettings.availableUIModes}
  105. />
  106. </div>
  107. {isLedgerCapable() && (
  108. <div className='ui--row'>
  109. <Dropdown
  110. defaultValue={ledgerConn}
  111. help={t<string>('Manage your connection to Ledger S')}
  112. label={t<string>('manage hardware connections')}
  113. onChange={_handleChange('ledgerConn')}
  114. options={ledgerConnOptions}
  115. />
  116. </div>
  117. )}
  118. <div className='ui--row'>
  119. <Dropdown
  120. defaultValue={i18nLang}
  121. label={t<string>('default interface language')}
  122. onChange={_handleChange('i18nLang')}
  123. options={translateLanguages}
  124. />
  125. </div>
  126. </>
  127. )}
  128. <Button.Group>
  129. {isModalContent && (
  130. <ButtonCancel onClick={onClose} />
  131. )}
  132. <Button
  133. icon='save'
  134. isDisabled={changed === null}
  135. label={
  136. changed
  137. ? t<string>('Save & Reload')
  138. : t<string>('Save')
  139. }
  140. onClick={
  141. changed
  142. ? _saveAndReload
  143. : _save
  144. }
  145. />
  146. </Button.Group>
  147. </div>
  148. );
  149. }
  150. export default React.memo(General);