Skip to content

Commit

Permalink
chore: refactor patron from <InputFile/> into <Step1/> (#529)
Browse files Browse the repository at this point in the history
Co-authored-by: o-tsaruk <oleksandr.v.tsaruk@gmail.com>
  • Loading branch information
peetzweg and o-tsaruk committed Mar 28, 2024
1 parent 37f008b commit 8b78644
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 67 deletions.
28 changes: 28 additions & 0 deletions src/lib/fileToFileState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2023 @paritytech/contracts-ui authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { FileState } from '../types';

export const fileToFileState = (file: File): Promise<FileState> =>
new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onabort = reject;
reader.onerror = reject;

reader.onload = ({ target }: ProgressEvent<FileReader>): void => {
if (target && target.result) {
const name = file.name;
const data = new Uint8Array(target.result as ArrayBuffer);
const size = data.length;

resolve({
data,
name,
size,
} as FileState);
}
};

reader.readAsArrayBuffer(file);
});
48 changes: 48 additions & 0 deletions src/lib/getContractFromPatron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2023 @paritytech/contracts-ui authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { Buffer } from 'buffer';

function getFromPatron(field: string, hash: string) {
const options = {
method: 'GET',
headers:
field === 'metadata'
? {
'Content-Type': 'application/json',
}
: {
'Content-Type': 'text/plain; charset=UTF-8',
},
mode: 'cors' as RequestMode,
};

return fetch('https://api.patron.works/buildSessions/' + field + '/' + hash, options).then(
response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return field === 'metadata' ? response.json() : response.arrayBuffer();
},
);
}

export function getContractFromPatron(codeHash: string): Promise<File> {
const metadataPromise = getFromPatron('metadata', codeHash);
const wasmPromise = getFromPatron('wasm', codeHash);
return Promise.all([metadataPromise, wasmPromise]).then(([metadataResponse, wasmResponse]) => {
const result = Buffer.from(wasmResponse as ArrayBuffer).toString('hex');

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
metadataResponse.source.wasm = '0x' + result;

const metadataString = JSON.stringify(metadataResponse);

const blob = new Blob([metadataString], { type: 'application/json' });
const patronFile = new File([blob], 'patron-contract.json', {
lastModified: new Date().getTime(),
type: 'json',
});
return patronFile;
});
}
79 changes: 22 additions & 57 deletions src/ui/components/form/InputFile.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,40 @@
/* eslint-disable header/header */
// Copyright 2021 @paritytech/contracts-ui authors & contributors
// Copyright 2017-2021 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 @paritytech/contracts-ui authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { createRef, useCallback, useEffect, useState } from 'react';
import Dropzone, { DropzoneRef } from 'react-dropzone';
import { XIcon } from '@heroicons/react/solid';
import { DocumentTextIcon } from '@heroicons/react/outline';
import type { FileState, InputFileProps as Props, OrFalsy } from 'types';
import { onDropPatronFile } from 'ui/components/metadata/GetPatronMetadata';

const NOOP = (): void => undefined;
import { XIcon } from '@heroicons/react/solid';
import { useCallback } from 'react';
import Dropzone from 'react-dropzone';
import { fileToFileState } from 'lib/fileToFileState';
import type { InputFileProps as Props } from 'types';

export function InputFile({
className = '',
errorMessage,
value: propsFile,
isError,
onChange,
placeholder,
onRemove,
placeholder,
value: file,
}: Props) {
const ref = createRef<DropzoneRef>();
const [file, setFile] = useState<OrFalsy<FileState>>(propsFile);

const onDrop = useCallback(
(files: File[]): void => {
files.forEach((file): void => {
const reader = new FileReader();

reader.onabort = NOOP;
reader.onerror = NOOP;

reader.onload = ({ target }: ProgressEvent<FileReader>): void => {
if (target && target.result) {
const name = file.name;
const data = new Uint8Array(target.result as ArrayBuffer);
const size = data.length;

onChange && onChange({ data, name, size });
ref &&
!propsFile &&
setFile({
data,
name,
size: data.length,
});
}
};

reader.readAsArrayBuffer(file);
(droppedFiles: File[]): void => {
// should only be one files as we set multiple={false} on <Dropzone/>
droppedFiles.forEach((droppedFile): void => {
fileToFileState(droppedFile)
.then(fileState => {
onChange && onChange(fileState);
})
.catch(e => console.error(e));
});
},
[ref, onChange, propsFile],
[onChange],
);

const removeHandler = useCallback((): void => {
onRemove && onRemove();
}, [onRemove]);

!propsFile && setFile(undefined);
}, [onRemove, propsFile]);

useEffect((): void => {
const params = new URL(window.location.href).searchParams;
const patronCodeHash = params.get('patron');
if (patronCodeHash && file?.name !== 'patron-contract.json') {
onDropPatronFile(patronCodeHash, onDrop);
}

if (file !== propsFile) {
setFile(propsFile);
}
}, [file, propsFile, onDrop]);

return file ? (
<div className={`${className} flex`} data-cy="upload-confirmation">
Expand All @@ -80,9 +43,11 @@ export function InputFile({
aria-hidden="true"
className="mr-2 h-7 w-7 justify-self-start text-gray-500"
/>

<span className="min-w-600 mr-20 justify-self-start text-xs text-gray-500 dark:text-gray-300">
{file.name} ({(file.size / 1000).toFixed(2)}kb)
</span>

{errorMessage && isError && (
<span className="min-w-600 mr-20 justify-self-start text-xs text-gray-500 dark:text-gray-300">
{errorMessage}
Expand All @@ -96,7 +61,7 @@ export function InputFile({
</div>
</div>
) : (
<Dropzone multiple={false} onDrop={onDrop} ref={ref}>
<Dropzone multiple={false} onDrop={onDrop}>
{({ getInputProps, getRootProps }) => {
return (
<div className={className} {...getRootProps()}>
Expand Down
13 changes: 3 additions & 10 deletions src/ui/components/form/hooks/useMetadataField.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright 2022-2024 @paritytech/contracts-ui authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { useState, useMemo, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router';
import { FileState, OrFalsy, UseMetadata } from 'types';
import { useMetadata } from 'ui/hooks/useMetadata';
import { useDatabase } from 'ui/contexts';
import { useDbQuery } from 'ui/hooks';
import { useMetadata } from 'ui/hooks/useMetadata';

interface UseMetadataField extends UseMetadata {
file: OrFalsy<FileState>;
Expand All @@ -15,7 +15,6 @@ interface UseMetadataField extends UseMetadata {
}

export const useMetadataField = (): UseMetadataField => {
const navigate = useNavigate();
const { db } = useDatabase();
const { codeHash: codeHashUrlParam } = useParams<{ codeHash: string }>();

Expand All @@ -33,12 +32,6 @@ export const useMetadataField = (): UseMetadataField => {

const isStored = useMemo((): boolean => !!codeBundle, [codeBundle]);

useEffect((): void => {
if (codeHashUrlParam && !codeBundle && !isLoading) {
navigate(`/instantiate/${codeHashUrlParam}`);
}
}, [codeBundle, codeHashUrlParam, isLoading, navigate]);

return {
file,
isLoading: isLoading && !!codeHashUrlParam,
Expand Down
18 changes: 18 additions & 0 deletions src/ui/components/instantiate/Step1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ import { useNonEmptyString } from 'ui/hooks/useNonEmptyString';
import { useApi, useDatabase, useInstantiate } from 'ui/contexts';
import { useDbQuery } from 'ui/hooks';

import { fileToFileState } from 'lib/fileToFileState';
import { getContractFromPatron } from 'lib/getContractFromPatron';

export function Step1() {
const { codeHash: codeHashUrlParam } = useParams<{ codeHash: string }>();
const { db } = useDatabase();
const [codeBundle] = useDbQuery(
() => (codeHashUrlParam ? db.codeBundles.get({ codeHash: codeHashUrlParam }) : undefined),
[codeHashUrlParam, db],
);

const { accounts } = useApi();
const { setStep, setData, data, step } = useInstantiate();

Expand All @@ -37,6 +41,19 @@ export function Step1() {
...metadataValidation
} = useMetadataField();

useEffect(() => {
const patronCodeHash = new URL(window.location.href).searchParams.get('patron');

if (!codeHashUrlParam && patronCodeHash) {
getContractFromPatron(patronCodeHash)
.then(fileToFileState)
.then(patronFileState => {
onChange(patronFileState);
})
.catch(e => console.error(`Failed fetching contract from Patron.works: ${e}`));
}
}, [codeHashUrlParam]);

useEffect(
function updateNameFromMetadata(): void {
if (metadataValidation.name && !name && !nameValidation.isTouched) {
Expand Down Expand Up @@ -103,6 +120,7 @@ export function Step1() {
<CodeHash codeHash={codeHashUrlParam} name={codeBundle.name} />
</FormField>
)}

{(!codeHashUrlParam || !isStored) && (
<FormField
help={
Expand Down

0 comments on commit 8b78644

Please sign in to comment.