7-2. Thành phần UploadPhoto

  1. Chức năng của thành phần UploadPhoto

  2. Mã thành phần

  3. Tương tác với hợp đồng

  4. Cập nhật dữ liệu vào cửa hàng: hàm updateFeed

1) Chức năng của thành phần UploadPhoto

Thành phần UploadPhoto xử lý yêu cầu tải ảnh lên blockchain Klaytn. Quy trình như sau:

  1. Gọi phương pháp uploadPhoto của hợp đồng thông minh bằng cách gửi một giao dịch. Trong phương pháp hợp đồng uploadPhoto, một token ERC-721 mới được tạo.

  2. Sau khi gửi một giao dịch, hãy cho thấy tiến trình cùng vòng đời giao dịch bằng thành phần Toast.

  3. Khi giao dịch tiến vào một khối, hãy cập nhật PhotoData mới trong cửa hàng redux cục bộ.

Limiting content size Kích thước tối đa của một giao dịch là 32KB. Do đó, chúng tôi hạn chế kích thước của dữ liệu đầu vào (ảnh và mô tả) không vượt quá 30KB để quá trình gửi đi diễn ra an toàn.

  • Quy mô dữ liệu chuỗi được giới hạn ở 2KB

  • Ảnh được nén để có kích thước nhỏ hơn 28KB bằng hàm imageCompression().

2) Mã thành phần

// src/components/UploadPhoto.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import imageCompression from 'utils/imageCompression';
import ui from 'utils/ui'
import Input from 'components/Input'
import InputFile from 'components/InputFile'
import Textarea from 'components/Textarea'
import Button from 'components/Button'

import * as photoActions from 'redux/actions/photos'

import './UploadPhoto.scss'

// Đặt giới hạn nội dung
const MAX_IMAGE_SIZE = 30 * 1024 // 30KB
const MAX_IMAGE_SIZE_MB = 30 / 1024 // 30KB

class UploadPhoto extends Component {
  state = {
    file: '',
    fileName: '',
    location: '',
    caption: '',
    warningMessage: '',
    isCompressing: false,
  }

  handleInputChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value,
    })
  }

  handleFileChange = (e) => {
    const file = e.target.files[0]

    /**
     * Nếu kích thước ảnh lớn hơn MAX_IMAGE_SIZE(30KB),
     * Nén ảnh để tải trên giao dịch
     * cf. Kích thước dữ liệu đầu vào giao dịch tối đa: 32KB
     */
    if (file.size > MAX_IMAGE_SIZE) {
      this.setState({
        isCompressing: true,
      })
      return this.compressImage(file)
    }

    return this.setState({
      file,
      fileName: file.name,
    })
  }

  handleSubmit = (e) => {
    e.preventDefault()
    const { file, fileName, location, caption } = this.state
    this.props.uploadPhoto(file, fileName, location, caption)
    ui.hideModal()
  }

  compressImage = async (imageFile) => {
    try {
      const compressedFile = await imageCompression(imageFile, MAX_IMAGE_SIZE_MB)
      this.setState({
        isCompressing: false,
        file: compressedFile,
        fileName: compressedFile.name,
      })
    } catch (error) {
      this.setState({
        isCompressing: false,
        warningMessage: '* Nén ảnh không thành công'
      })
    }
  }

  render() {
    const { fileName, location, caption, isCompressing, warningMessage } = this.state
    return (
      <form className="UploadPhoto" onSubmit={this.handleSubmit}>
        <InputFile
          className="UploadPhoto__file"
          name="file"
          label="Search file"
          fileName={isCompressing ? "Đang nén ảnh..." : fileName}
          onChange={this.handleFileChange}
          err={warningMessage}
          accept=".png, .jpg, .jpeg"
          required
        />
        <Input
          className="UploadPhoto__location"
          name="location"
          label="Location"
          value={location}
          onChange={this.handleInputChange}
          placeholder="Bạn đã chụp ảnh này ở đâu?"
          required
        />
        <Textarea
          className="UploadPhoto__caption"
          name="caption"
          value={caption}
          label="Caption"
          onChange={this.handleInputChange}
          placeholder="Upload your memories"
          required
        />
        <Button
          className="UploadPhoto__upload"
          type="submit"
          title="Upload"
        />
      </form>
    )
  }
}

const mapDispatchToProps = (dispatch) => ({
  uploadPhoto: (file, fileName, location, caption) =>
    dispatch(photoActions.uploadPhoto(file, fileName, location, caption)),
})

export default connect(null, mapDispatchToProps)(UploadPhoto)

3) Tương tác với hợp đồng

Hãy tạo một hàm để viết dữ liệu ảnh lên Klaytn. Send transaction to contract: uploadPhoto Không giống các lệnh gọi hàm Read-only, việc viết dữ liệu làm phát sinh phí giao dịch. Phí giao dịch được xác định bằng lượng gas đã sử dụng. gas là đơn vị đo thể hiện số lượng phép tính cần để xử lý giao dịch.

Vì những lý do này, việc gửi một giao dịch cần hai thuộc tính fromgas.

  1. Chuyển đổi tập tin ảnh thành một chuỗi byte để tải trên giao dịch

    (Trong Klaystagram contract, chúng ta đã định nghĩa định dạng ảnh là byte trong cấu trúc PhotoData)

    • Đọc dữ liệu ảnh dưới dạng ArrayBuffer bằng FileReader

    • Chuyển đổi ArrayBuffer thành chuỗi số hex

    • Thêm tiền tố 0x để thỏa mãn định dạng byte

  2. Gọi phương pháp hợp đồng: uploadPhoto

    • from: Một tài khoản gửi giao dịch này và thanh toán phí giao dịch.

    • gas: Lượng gas tối đa mà tài khoản from sẵn sàng thanh toán cho giao dịch này.

  3. Sau khi gửi giao dịch, hiển thị tiến trình cùng vòng đời giao dịch bằng thành phần Toast.

  4. Nếu giao dịch thành công tiến vào một khối, gọi hàm updateFeed để thêm ảnh mới vào trang nguồn cấp dữ liệu.

// src/redux/actions/photo.js

export const uploadPhoto = (
  file,
  fileName,
  location,
  caption
) => (dispatch) => {
  // 1. Chuyển đổi tập tin ảnh thành chuỗi số hex để tải trên giao dịch
  const reader = new window.FileReader()
  reader.readAsArrayBuffer(file)
  reader.onloadend = () => {
    const buffer = Buffer.from(reader.result)

    // Thêm tiền tố "0x" vào hexString để hợp đồng nhận diện hexString là byte
    const hexString = "0x" + buffer.toString('hex')

    // 2. Gọi phương pháp hợp đồng: uploadPhoto
    // Gửi giao dịch với tập tin ảnh (hexString) và mô tả
    try{
      KlaystagramContract.methods.uploadPhoto(hexString, fileName, location, caption).send({
        from: getWallet().address,
        gas: '200000000',
      }, (error, txHash) => {
        if (error) throw error;

        // 3. Sau khi gửi giao dịch,
        // hiển thị tiến trình cùng vòng đời giao dịch bằng thành phần "Toast".
        ui.showToast({
          trạng thái: 'đang chờ',
          message: `Gửi một giao dịch... (uploadPhoto)`,
          txHash,
        })
      })
        .then((receipt) => {
          ui.showToast({
            trạng thái: receipt.trạng thái ? 'success' : 'fail',
            message: `Đã nhận biên lai! Điều đó nghĩa là giao dịch của bạn
            ở trong khối klaytn (#${receipt.blockNumber}) (uploadPhoto)`,
            link: receipt.transactionHash,
          })

          // 4. Nếu giao dịch của bạn thành công tiến vào một khối,
          // gọi hàm "updateFeed" để thêm ảnh mới vào trang nguồn cấp dữ liệu.
          if(receipt.trạng thái) {
            const tokenId = receipt.events.PhotoUploaded.returnValues[0]
            dispatch(updateFeed(tokenId))
          }
        })
    } catch (error) {
      ui.showToast({
        trạng thái: 'error',
        message: error.toString(),
      })
    }
  }
}

cf) Vòng đời giao dịch

Sau khi gửi giao dịch, bạn có thể lấy vòng đời giao dịch (transactionHash, receipt, error).

  • Sau khi phiên bản giao dịch đã ký của bạn được xây dựng đúng cách, sự kiện transactionHash sẽ được kích hoạt. Bạn có thể lấy hàm băm của giao dịch trước khi gửi giao dịch lên mạng lưới.

  • Sự kiện receipt sẽ được kích hoạt khi bạn nhận được biên lai giao dịch. Điều đó nghĩa là giao dịch của bạn ở trong một khối. Bạn có thể xem số khối bằng receipt.blockNumber.

  • Sự kiện error được kích hoạt khi có lỗi xảy ra.

4. Tải ảnh lên trang nguồn cấp dữ liệu: updateFeed

Sau khi thành công gửi giao dịch vào hợp đồng, FeedPage cần được cập nhật. Để cập nhật nguồn cấp dữ liệu ảnh, ta cần lấy dữ liệu ảnh mới ta vừa tải lên. Hãy gọi getPhoto() bằng tokenId. tokenId có thể được truy xuất từ biên lai giao dịch. Sau đó, thêm dữ liệu ảnh mới vào cửa hàng redux cục bộ.

// src/redux/actions/photo.js

/**
 * 1. Gọi phương pháp hợp đồng: getPhoto()
 * Để lấy dữ liệu ảnh mới ta vừa tải lên,
 * gọi hàm "getPhoto()" bằng tokenId từ biên lai sau khi gửi giao dịch
*/
const updateFeed = (tokenId) => (dispatch, getState) => {
  KlaystagramContract.methods.getPhoto(tokenId).call()
    .then((newPhoto) => {
      const { photos: { feed } } = getState()
      const newFeed = [feedParser(newPhoto), ...feed]

      // 2. cập nhật nguồn cấp dữ liệu mới vào cửa hàng
      dispatch(setFeed(newFeed))
    })
}

Last updated