Sharing of user types (#4210)
* Sharing of user types * url encoding/decoding * assert non-array * Developer page URL encoding * Cleanup saving/loading encoding * Move fflate * CopyButton
This commit is contained in:
parent
de747bca9d
commit
fe31bc522d
@ -321,7 +321,7 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
|
||||
onChange={_selectSeedType}
|
||||
options={seedOpt}
|
||||
/>
|
||||
< CopyButton
|
||||
<CopyButton
|
||||
className='copyMoved'
|
||||
type={seedType === 'bip' ? t<string>('mnemonic') : seedType === 'raw' ? isEthereum ? t<string>('private key') : 'seed' : t<string>('raw seed')}
|
||||
value={seed}
|
||||
|
||||
@ -9,7 +9,8 @@ import store from 'store';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { registry } from '@polkadot/react-api';
|
||||
import { Button, Editor, InputFile } from '@polkadot/react-components';
|
||||
import { decodeUrlTypes, encodeUrlTypes } from '@polkadot/react-api/urlTypes';
|
||||
import { Button, CopyButton, Editor, InputFile } from '@polkadot/react-components';
|
||||
import { isJsonObject, stringToU8a, u8aToString } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
@ -37,14 +38,16 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
|
||||
const [isTypesValid, setIsTypesValid] = useState(true);
|
||||
const [types, setTypes] = useState<Record<string, any>>(EMPTY_TYPES);
|
||||
const [typesPlaceholder, setTypesPlaceholder] = useState<string | null>(null);
|
||||
const [sharedUrl, setSharedUrl] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
const types = store.get('types') as Record<string, unknown> || {};
|
||||
const types = decodeUrlTypes() || store.get('types') as Record<string, unknown> || {};
|
||||
|
||||
if (Object.keys(types).length) {
|
||||
setCode(JSON.stringify(types, null, 2));
|
||||
setTypes({});
|
||||
setTypesPlaceholder(Object.keys(types).join(', '));
|
||||
setSharedUrl(encodeUrlTypes(types));
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -125,6 +128,8 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
|
||||
|
||||
const _saveDeveloper = useCallback(
|
||||
(): void => {
|
||||
let url = null;
|
||||
|
||||
try {
|
||||
registry.register(types);
|
||||
store.set('types', types);
|
||||
@ -133,6 +138,12 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
|
||||
action: t<string>('Your custom types have been added'),
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
if (Object.keys(types).length) {
|
||||
url = encodeUrlTypes(types);
|
||||
|
||||
console.log(url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setIsTypesValid(false);
|
||||
@ -141,6 +152,8 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
setSharedUrl(url);
|
||||
},
|
||||
[onStatusChange, t, types]
|
||||
);
|
||||
@ -180,6 +193,11 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
|
||||
</div>
|
||||
</div>
|
||||
<Button.Group>
|
||||
<CopyButton
|
||||
label={t<string>('Share')}
|
||||
type={t<string>('url')}
|
||||
value={sharedUrl}
|
||||
/>
|
||||
<Button
|
||||
icon='sync'
|
||||
label={t<string>('Reset')}
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@polkadot/api": "^3.0.1",
|
||||
"@polkadot/extension-dapp": "^0.35.2-12",
|
||||
"fflate": "^0.4.2",
|
||||
"rxjs-compat": "^6.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import { defaults as addressDefaults } from '@polkadot/util-crypto/address/defau
|
||||
|
||||
import ApiContext from './ApiContext';
|
||||
import registry from './typeRegistry';
|
||||
import { decodeUrlTypes } from './urlTypes';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
@ -66,7 +67,7 @@ function isKeyringLoaded () {
|
||||
}
|
||||
|
||||
function getDevTypes (): Record<string, Record<string, string>> {
|
||||
const types = store.get('types', {}) as Record<string, Record<string, string>>;
|
||||
const types = decodeUrlTypes() || store.get('types', {}) as Record<string, Record<string, string>>;
|
||||
const names = Object.keys(types);
|
||||
|
||||
names.length && console.log('Injected types:', names.join(', '));
|
||||
|
||||
37
packages/react-api/src/urlTypes.ts
Normal file
37
packages/react-api/src/urlTypes.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { unzlibSync, zlibSync } from 'fflate';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import uiSettings from '@polkadot/ui-settings';
|
||||
import { assert, stringToU8a, u8aToString } from '@polkadot/util';
|
||||
import { base64Decode, base64Encode } from '@polkadot/util-crypto';
|
||||
|
||||
export function decodeUrlTypes (): Record<string, any> | null {
|
||||
const urlOptions = queryString.parse(location.href.split('?')[1]);
|
||||
|
||||
if (urlOptions.types) {
|
||||
try {
|
||||
assert(!Array.isArray(urlOptions.types), 'Expected a single type specification');
|
||||
|
||||
const parts = urlOptions.types.split('#');
|
||||
const compressed = base64Decode(decodeURIComponent(parts[0]));
|
||||
const uncompressed = unzlibSync(compressed);
|
||||
|
||||
return JSON.parse(u8aToString(uncompressed)) as Record<string, any>;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function encodeUrlTypes (types: Record<string, any>): string {
|
||||
const jsonU8a = stringToU8a(JSON.stringify(types));
|
||||
const compressed = zlibSync(jsonU8a, { level: 9 });
|
||||
const encoded = base64Encode(compressed);
|
||||
|
||||
return `${window.location.origin}${window.location.pathname}?rpc=${encodeURIComponent(uiSettings.apiUrl)}&types=${encodeURIComponent(encoded)}`;
|
||||
}
|
||||
@ -30,4 +30,8 @@ export default React.memo(styled(ButtonGroup)`
|
||||
.ui--Button {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.ui--CopyButton {
|
||||
display: inline-block;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -15,6 +15,7 @@ interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
icon?: IconName;
|
||||
label?: React.ReactNode;
|
||||
type?: string;
|
||||
isMnemonic?: boolean;
|
||||
value: string;
|
||||
@ -22,20 +23,19 @@ interface Props {
|
||||
|
||||
const NOOP = () => undefined;
|
||||
|
||||
function CopyButton ({ children, className = '', icon = 'copy', type, value }: Props): React.ReactElement<Props> {
|
||||
function CopyButton ({ children, className = '', icon = 'copy', label, type, value }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { queueAction } = useContext(StatusContext);
|
||||
|
||||
const _onCopy = useCallback(
|
||||
(): void => {
|
||||
(type !== 'other') && queueAction && queueAction({
|
||||
account: type !== 'mnemonic' ? value : undefined,
|
||||
queueAction && queueAction({
|
||||
action: t<string>('clipboard'),
|
||||
message: t<string>('{{type}} copied', { replace: { type: type || t<string>('other') } }),
|
||||
message: t<string>('{{type}} copied', { replace: { type: type || t<string>('value') } }),
|
||||
status: 'queued'
|
||||
});
|
||||
},
|
||||
[type, queueAction, t, value]
|
||||
[type, queueAction, t]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -50,6 +50,8 @@ function CopyButton ({ children, className = '', icon = 'copy', type, value }: P
|
||||
<Button
|
||||
className='icon-button show-on-hover'
|
||||
icon={icon}
|
||||
isDisabled={!value}
|
||||
label={label}
|
||||
onClick={NOOP}
|
||||
/>
|
||||
</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user