Create WEB APP "MEME of the day" use IPFS network and REACT step by step

WEB APP "MEME of the day!"


meme-web-app - ссылка на веб приложение в сети IPFS.
git repository - ссылка на репозиторий в github.

Создадим веб приложение "Мем дня". Для создания веб приложения буду использовать библиотеку React, для выхода в сеть IPFS использую шлюз infura.io, для пользовательского интерфейса фреймворк material-ui. Разработка ведется в среде Linux Ubuntu 18.04.5 LTS. Также я предполагаю что пользователь знаком с работой React и языковом стандартом ECMAScript6 и выше.

Для начала нужно установить (в случаи отсутствия) среду выполнения JavaScript NodeJS и менеджер пакетов npm после этого установить пакет create-react-app. Этот инструмент позволяет настроить среду для разработки React, выполнив команду:
$ npm i -g create-react-app

флаг -g устанавливает пакет глобально для этого нужно обладать правами администратора.
Создадим шаблонное веб приложение выполнив команду:

$ create-react-app meme-web-app 

В результате будет создана директория meme-web-app с необходимыми зависимостями, директориями и файлами. Переходим в созданную директорию и запустим редактор, я пользуюсь visual studio code.

$ cd meme-web-app
meme-web-app$ code .

На рисунке ниже отображен интерфейс редактора visual studio code в директории meme-web-app и исходный код в файле App.js, править будем только этот файл.
Снимок экрана от 20210301 140647.png
В редакторе кода откройте дополнительное окно с терминалом и выполните команду npm start. React предлагает использование менеджера пакетов yarn, но я буду пользоваться npm.

meme-web-app$ npm start

Подождите несколько секунд и по окончанию выполнения команды в терминале выведется следующее сообщение:

Compiled successfully!

You can now view my-web-app in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.0.47:3000

Note that the development build is not optimized.
To create a production build, use yarn build.

Здесь IP адрес может отличаться от Вашего, после этого откроется браузер по умолчанию с адресом http://localhost:3000.
Скриншот запущенного веб приложения.
Снимок экрана от 20210227 214713.png
Подготовительная часть окончена, приступим к правке кода но для начала установим библиотеку material-ui и ipfs клиент.

meme-web-app$ npm install @material-ui/core
meme-web-app$ npm install ipfs-http-client

Основной файл которой необходимо править это App.js. Ниже представлен код который лишь отображает интерфейс без какого-либо функционала.

import { makeStyles } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Container from "@material-ui/core/Container";
import Grid from "@material-ui/core/Grid";
import Input from "@material-ui/core/Input";
import InputLabel from "@material-ui/core/InputLabel";
import Button from "@material-ui/core/Button";

const useStyles = makeStyles((theme) => ({
  title: {
    flexGrow: 1,
  },
  input: {
    display: "none",
  },
  paper: {
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    marginTop: theme.spacing(4),
    width: theme.spacing(90),
    height: theme.spacing(80),
  },
}));

function App() {
  const classes = useStyles();

  return (
    <div>
      <Container>
        <AppBar position="static">
          <Toolbar>
            <Typography className={classes.title}>
              Meme of the day!!!
            </Typography>
            <Input
              inputProps={{ accept: "image/*" }}
              type="file"
              id="add_meme"
              className={classes.input}
            />
            <InputLabel htmlFor="add_meme">
              <Button variant="contained" color="secondary" component="span">
                ADD MEME
              </Button>
            </InputLabel>
          </Toolbar>
        </AppBar>
        <Typography variant="h3" align="center">
          Meme on the topic of the day
        </Typography>
        <Grid justify="center" container>
          <Paper className={classes.paper} elevation={3}>
            <img src=" />
          </Paper>
        </Grid>
      </Container>
    </div>
  );
}

export default App;

Получился вот такой интерфейс где изображение я вставил с любого доступного ресурса
Снимок экрана от 20210301 193422.png
Займемся функционалом загрузки файла в сеть ipfs. В тег <Input/> добавим атрибуты type="file", onChange={changeHandler}. changeHandler это функция, которая выполняется при добавления файла.

<Input
  onChange={changeHandler}
  inputProps={{ accept: "image/*" }}
  type="file"
  id="add_meme"
  className={classes.input}
/>

Теперь создадим функцию changeHandler(), при этом надо отметить функция асинхронная для того чтобы получить ответ после загрузки файла в сеть IPFS. На событие применяем функцию preventDefault(), чтобы не обновлялась страница. В этой функции будем использовать клиент ipfs-http-client давайте его импортируем и введем параметры.

import * as ipfsClient from "ipfs-http-client";

const ipfs = ipfsClient({
  host: "ipfs.infura.io",
  port: "5001",
  protocol: "https",
});

Также добавляем useState для хранения ссылки на файл в сети IPFS между рендерами страницы. Более подробно как использовать useState можно ознакомиться в документации React.

import { useState } from "react";
const [memeHash, setMemeHash] = useState("");

Теперь напишем нашу функцию changeHandler() с предыдущими уточнениями.

const changeHandler = async (event) => {
    event.preventDefault();
    const memeFile = event.target.files[0];
    const res = await ipfs.add(memeFile, {
      progress: (prog) => console.log(prog),
    });
    const resV1 = res.cid.toV1();
    setMemeHash(toBase32(resV1));
  };

Если вы заметили в коде применяется функция toBase32(), так зачем она нужна? Функция ipfs.add() возвращает нам CID. CID это идентификатор контента, который основан на криптографическом хэше контента, различают две версии CIDv0 and CIDv1. Так вот функция ipfs.add() возвращает версия CIDv0 для того чтобы преобразовать CIDv0 в CIDv1 вызываю функцию toBase32(). Преобразование делается в целях безопасности.

import { CID } from "ipfs-http-client";
const toBase32 = (value) => {
  const cid = new CID(value);
  return cid.toV1().toBaseEncodedString("base32");
};

Теперь нам нужно отобразить наш мем на странице в теге <img src=``/> с помощью строкового литерала.

<img src={`https://${memeHash}.ipfs.infura-ipfs.io/`} />

Основную работу мы завершили, добавляем несколько строк для показа прогресса загрузки файла сеть IPFS.

import LinearProgress from "@material-ui/core/LinearProgress";
import Box from "@material-ui/core/Box";
const [progress, setProgress] = useState(0);
const [sizeFile, setSizeFile] = useState(0);

const normalise = (value) => (value * 100) / sizeFile;

Преобразуем функцию changeHandler(), всё также для показа прогресса загрузки.

const changeHandler = async (event) => {
  event.preventDefault();
  const memeFile = event.target.files[0];
  setSizeFile(memeFile.size);
  const res = await ipfs.add(memeFile, {
    progress: (prog) => setProgress(prog),
  });
  setProgress(0);
  const resV1 = res.cid.toV1();
  setMemeHash(toBase32(resV1));
};

Добавляем наш прогресс загрузки на страницу.

{!!progress ? (
  <LinearProgress
    color="secondary"
    variant="determinate"
    value={normalise(progress)}
  />
) : (
  <Box className={classes.box}></Box>
)}

Итоговый код в файле App.js

import { useState } from "react";
import * as ipfsClient from "ipfs-http-client";
import { CID } from "ipfs-http-client";
import { makeStyles } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Container from "@material-ui/core/Container";
import Grid from "@material-ui/core/Grid";
import Input from "@material-ui/core/Input";
import InputLabel from "@material-ui/core/InputLabel";
import Button from "@material-ui/core/Button";
import LinearProgress from "@material-ui/core/LinearProgress";
import Box from "@material-ui/core/Box";

const useStyles = makeStyles((theme) => ({
  title: {
    flexGrow: 1,
  },
  input: {
    display: "none",
  },
  box: {
    height: "4px",
  },
  paper: {
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    marginTop: theme.spacing(1),
    width: theme.spacing(90),
    height: theme.spacing(80),
  },
}));

function App() {
  const classes = useStyles();
  const [memeHash, setMemeHash] = useState(
    "bafybeigc5k5hriuejo5ccdre7cnmcppml2vmx4ffjfw7juiai6nusdye7u"
  );
  const [progress, setProgress] = useState(0);
  const [sizeFile, setSizeFile] = useState(0);

  const ipfs = ipfsClient({
    host: "ipfs.infura.io",
    port: "5001",
    protocol: "https",
  });

  const normalise = (value) => (value * 100) / sizeFile;

  const toBase32 = (value) => {
    const cid = new CID(value);
    return cid.toV1().toBaseEncodedString("base32");
  };

  const changeHandler = async (event) => {
    event.preventDefault();
    const memeFile = event.target.files[0];
    setSizeFile(memeFile.size);
    const res = await ipfs.add(memeFile, {
      progress: (prog) => setProgress(prog),
    });
    setProgress(0);
    const resV1 = res.cid.toV1();
    setMemeHash(toBase32(resV1));
  };

  return (
    <div>
      <Container>
        <AppBar position="static">
          <Toolbar>
            <Typography className={classes.title}>
              Meme of the day!!!
            </Typography>
            <Input
              onChange={changeHandler}
              inputProps={{ accept: "image/*" }}
              type="file"
              id="add_meme"
              className={classes.input}
            />
            <InputLabel htmlFor="add_meme">
              <Button variant="contained" color="secondary" component="span">
                ADD MEME
              </Button>
            </InputLabel>
          </Toolbar>
        </AppBar>
        {!!progress ? (
          <LinearProgress
            color="secondary"
            variant="determinate"
            value={normalise(progress)}
          />
        ) : (
          <Box className={classes.box}></Box>
        )}
        <Typography variant="h3" align="center">
          Meme on the topic of the day
        </Typography>
        <Grid justify="center" container>
          <Paper className={classes.paper} elevation={3}>
            <img src={`https://${memeHash}.ipfs.infura-ipfs.io`} />
          </Paper>
        </Grid>
      </Container>
    </div>
  );
}

export default App;

Данное веб приложение хостит в сети IPFS доступ по ссылке ниже.
meme-web-app
Предоставлена краткая инструкция по созданию веб приложения "Мем дня" с отправкой и хранения файлов в сети IPFS. Если есть вопросы пишите в комментарии.

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now
Ecency