0

We are using a clone of the Polkadot-JS/apps. Our users will need to have several contracts loaded to the UI in order to use the apps. We want our users to be able to add an existing contract to their UI without having to locate the file, drag and drop, etc. In a perfect world, we would love to have one button that auto-uploads the contract to the UI... but until we can figure that out...

Is there a way to pre-populate the metadata.json file in the modal used to add an existing contract? We have a button that says "load the app" which brings up a modal from Add.tsx to process the contract and add it to the UI.

We have narrowed it down to the InputFile.tsx file that governs the Dropzone in that modal. We plant to have the metadata.json local to the UI files. Is there a way to set the default file in the Dropzone? Here is the current file code...

// Copyright 2017-2023 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0

import React, { createRef, useCallback, useState } from 'react';
import Dropzone, { DropzoneRef } from 'react-dropzone';
import styled from 'styled-components';

import { formatNumber, hexToU8a, isHex, u8aToString } from '@polkadot/util';

import Labelled from './Labelled';
import { useTranslation } from './translate';

export interface InputFilePropsBase {
  className?: string;
  clearContent?: boolean;
  isDisabled?: boolean;
  isError?: boolean;
  isFull?: boolean;
  label: React.ReactNode;
  labelExtra?: React.ReactNode;
  placeholder?: React.ReactNode | null | false;
  withEllipsis?: boolean;
  withLabel?: boolean;
}

export interface InputFileProps extends InputFilePropsBase {
  // Reference Example Usage: https://github.com/react-dropzone/react-dropzone/tree/master/examples/Accept
  // i.e. MIME types: 'application/json, text/plain', or '.json, .txt'
  accept?: string;
  onChange?: (contents: Uint8Array, name: string) => void;
}

interface FileState {
  name: string;
  size: number;
}

const BYTE_STR_0 = '0'.charCodeAt(0);
const BYTE_STR_X = 'x'.charCodeAt(0);
const STR_NL = '\n';
const NOOP = (): void => undefined;

function convertResult (result: ArrayBuffer): Uint8Array {
  const data = new Uint8Array(result);

  // this converts the input (if detected as hex), via the hex conversion route
  if (data[0] === BYTE_STR_0 && data[1] === BYTE_STR_X) {
    let hex = u8aToString(data);

    while (hex[hex.length - 1] === STR_NL) {
      hex = hex.substring(0, hex.length - 1);
    }

    if (isHex(hex)) {
      return hexToU8a(hex);
    }
  }

  return data;
}

function InputFile ({ accept, className = '', clearContent, isDisabled, isError = false, isFull, label, labelExtra, onChange, placeholder, withEllipsis, withLabel }: InputFileProps): React.ReactElement<InputFileProps> {
  const { t } = useTranslation();
  const dropRef = createRef<DropzoneRef>();
  const [file, setFile] = useState<FileState | undefined>();

  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 = convertResult(target.result as ArrayBuffer);

            onChange && onChange(data, name);
            dropRef && setFile({
              name,
              size: data.length
            });
          }
        };

        reader.readAsArrayBuffer(file);
      });
    },
    [dropRef, onChange]
  );

  const dropZone = (
    <Dropzone
      accept={accept}
      disabled={isDisabled}
      multiple={false}
      onDrop={_onDrop}
      ref={dropRef}
    >
      {({ getInputProps, getRootProps }): JSX.Element => (
        <StyledDiv {...getRootProps({ className: `${className} ui--InputFile ${isError ? 'error' : ''}` })}>
          <input {...getInputProps()} />
          <em className='label'>
            {
              !file || clearContent
                ? placeholder || t<string>('click to select or drag and drop the file here')
                : placeholder || t<string>('{{name}} ({{size}} bytes)', {
                  replace: {
                    name: file.name,
                    size: formatNumber(file.size)
                  }
                })
            }
          </em>
        </StyledDiv>
      )}
    </Dropzone>
  );

  return label
    ? (
      <Labelled
        isFull={isFull}
        label={label}
        labelExtra={labelExtra}
        withEllipsis={withEllipsis}
        withLabel={withLabel}
      >
        {dropZone}
      </Labelled>
    )
    : dropZone;
}

const StyledDiv = styled.div`
  background: var(--bg-input);
  border: 1px solid var(--border-input);
  border-radius: 0.28571429rem;
  font-size: var(--font-size-base);
  margin: 0.25rem 0;
  padding: 0.67857143em 1em;
  width: 100% !important;

  &.error {
    background: var(--bg-input-error);
    border-color: #e0b4b4;
  }

  &:hover {
    cursor: pointer;
  }
`;

export default React.memo(InputFile);

1 Answer 1

2

I'm not an expert on the polkadotjs ui codebase but as you discovered, the app uses react-dropzone(docs) library for the <Dropzone />.

I think you could just potentially call the passed _onDrop function yourself with your metadata.json from code in a useEffect() hook. And potentially the accept method passed to the InputFile component.

But maybe that is even to much if you don't need the user to have the ability to choose a file manually. Without the possibility to upload a file you could just craft a user flow without the react-dropzone part and just use polkadotjs with the metadata you've bundled in your js app or fetched from a server in your code.

Hope this helps.

GitHub Issue: react-dropzone: initialize react dropzone with file #917

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.