0.0.7 • Published 2 years ago

@lefapps/uploader v0.0.7

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

UPLOADER

This react Component is an interface for uploading files. This component does not handle the upload itself, you should use your own upload logic like edgee:slingshot for Meteor apps.

This Component does support selecting files, previewing them and add metadata.

For images, auto-rotating, resizing and cropping is possible client side. (Note that this depends on the browser used by the client!)

Install

Install from npm:

$ npm install @lefapps/uploader --save

Usage

import React from 'react'
import Uploader, { Preview } from '@lefapps/uploader'

const FormComponent = ({ onChange }) => {
  const config = {
    // config of thumbnails for images
    sizes: [
      {
        label: 'banner-sm',
        width: 1280,
        height: 240,
        crop: true,
        retina: true,
        quality: 60
      }
    ],
    // logic to handle your file upload
    // e.g. Edgee Slingshot directive
    uploader: { send: (file, callback = (error, url) => {}) => {} }
  }
  return <Uploader {...config} onChange={onChange}>
}

Uploader

The Uploader renders the file input, together with selected files while they are being processed. After processing, they are passed to the onChange handler. It is up to you to display these files by using the Preview component, which can be passed as children to the Uploader.

class Form extends React.Component {
  render () {
    const bindInput = {
      onChange: value => this.setState(({ values }) =>
        ({ values: values.concat(value) })
      )
    }
    return
      <Uploader {...bindInput}>
        {this.state.values.map((value, index) =>
          <Preview {...value} key={index} />
        )}
      </Uploader>
  }
}

Preview

The Preview component is exposed to make it possible to render the already uploaded files consistent with the files being processed. It also accepts children where you can add functionality like sorting and removing.

<Preview url={} name={}>
  <Button onClick={remove}>x</Button>
</Preview>

The Uploader

Configuration

PropTypeRequiredDefaultDescription
labelStringComponentLabel to display above the file input
placeholderStringLabel to display inside the file input
nameStringName attribute of the file input element
multipleBoolfalseAllow multiple files to be selected for upload
uploaderObjectClassLogic to send the file to your storage of choiceSee below for specific information. Works seamlessly with edgee:slingshot directives.
sizesObjectThumbnail configuration for image files
invalidBoolWhether the input is valid or not
idStringId attribute for DOM reference
classNameStringClass attribute for custom styling
metaDataObjectOptional metadata to add to the file

Upload handler class

You should provide the upload handler yourself. It should have at least a method send which accepts the file and a callback. The callback expects error and result as arguments.

class UploadHandler {
  send (file, callback) {
    // ... send file to cloud storage
    if (error) callback(error)
    else callback(null, url)
  }
}

If you use a Meteor application, you can use edgee:slingshot.

import Uploader, { Preview } from '@lefapps/uploader'
import { Slingshot } from 'meteor/edgee:slingshot'

<Uploader {...props} uploader={new Slingshot.Upload('files')}>

Check edgee:slingshot documentation on how to set up directives.

Thumbnails

When you use the uploader to upload images, you can make this component to attempt creating thumbnails client-side. you can specify them through the sizes prop.

KeyTypeRequiredDefaultDescription
labelStringPrefix for this thumbnail, easy to reference laterE.g. square-sm, banner-lg, …
widthIntegerWidth of thumbnail
heightIntegerHeight of thumbnail
cropBoolfalseWhether the image should be cropped or resizedIf crop isn't true, width and height act as maximal dimensions; one edge of the thumbnail will be smaller to fit within specified size.
qualityNumber60Image quality between .25 and 1 or 25 and 100 %Numbers greater than 100 set the quality to maximum (100 %).
retinaBoolfalseCreate a retina version of the current thumbnailSize: double; Quality: 80 % of set quality.

The label of the size props will be used as a prefix for the uploaded thumbnail. The above examples will be stored as follows: https://your.cdn/uploader/square-sm/filename.jpeg

OnChange handler

After a succesful upload, the file is passed to the onChange handler on the component. This "file" is actually an object with various properties which you can use to your own liking.

Note: when the attribute multiple is set on the uploader, the onChange handler is called for every file.

KeyTypeDescription
nameStringUnique filename for all uploaded files
urlStringsuccess from the send callback, most likely the full url to the uploaded original
localBlobuse this to display the image in the preview to prevent bandwidth usage by downloading the uploaded file from url immediately after upload. Note: do not save this value in your database.
isImageBoolWhether the uploaded file was seen as an image
errorStringIf an error occured during the process, it will appear here

Image processing

When images are selected, the uploader performs the following actions:

  1. Auto rotate the image JPEGs can have 8 rotations. When such an image is displayed on a webpage, the orientation is not always corrected. This is handled by the uploader before uploading or generating thumbnails. This auto rotation is always performed.
  2. Generate thumbnails Based on your configuration, the requested thumbnails are created in browser using html canvas. If the user’s browser does not support the canvas element, no thumbnails are generated.
  3. Upload Originals and thumbnails are uploaded sequentially.
  4. Call onChange handler The handler is called once per selected image after a successful upload of the original and its thumbnails.

Example: a user selects 4 images and a pdf. The configuration requests 2 thumbnails per image.

  1. The four images are rotated if necessary.
  2. The 4 × 2 thumbnails are generated.
  3. 1 pdf, 4 original images and 8 thumbnails are uploaded.
  4. The handler is called 5 times. If n uploads fail, the handler is called 5 - n times.

The Preview

Simple usage: if you pass the onChange argument as ...props to the Preview component, it will be displayed fine.

Configuration

PropTypeRequiredDefaultDescription
nameString''Name of the file
urlString''Url of preview (image)If no preview is available, the file type will be shown instead.
localString''Urlstring of the local image, overrides url if present
errorString''Display an error message instead of the preview
childrenComponentExtend preview functionality to remove, sort, …
extrasComponentOptional children below the filenameUse this prop to add e.g. alt-tag and/or copyright information after upload.

Children

Example of sorting and removing:

class FormComponent extends React.Component {
  _getValue () {
    const { bindInput, element } = this.props
    return bindInput(element.name) // see @lefapps/forms
  }
  _modifyModel (action, { index, model, direction }) {
    let { name, value, onChange, multiple } = this._getValue()
    switch (action) {
      case 'create':
        multiple ? value.splice(value.length, 0, model) : (value = model)
        break
      case 'update':
        multiple ? value.splice(index, 1, model) : (value = model)
        break
      case 'delete':
        if (confirm('Are you sure you want to delete this file?')) {
          multiple ? value.splice(index, 1) : (value = undefined)
        }
        break
      case 'move':
        if (multiple) {
          value.splice(index, 1)
          value.splice(index + direction, 0, model)
        }
        break
    }
    this.setState({ value })
    return value
  }
  move = (index, direction) => {
    const model = this._getValue().value[index]
    this._modifyModel('move', { index, direction, model })
  }
  remove = index => {
    this._modifyModel('delete', { index })
  }
  canMove = (index, direction) => {
    const { value, multiple } = this.props.bindInput(this.props.element.name)
    return (
      multiple &&
      ((direction > 0 && index < value.length - 1) ||
        (direction < 0 && index > 0))
    )
  }
  render() {
    return
      <Uploader onChange={model => this._modifyModel('create', { model })} {...config}>
        {this.state.values.map((value, index) =>
          <Preview key={index} {...value}>
            <ButtonGroup>
              <Button onClick={() => this.move(index, -1)} disabled={!this.canMove(index, -1)}>
                △
              </Button>
              <Button onClick={() => this.move(index, 1)} disabled={!this.canMove(index, 1)} >
                ▽
              </Button>
              <Button onClick={() => this.remove(index)}>✕</Button>
            </ButtonGroup>
          </Preview>
        )}
      </Uploader>
  }
}

Extras

If you want even more options, you can an extra set of children to display below the filename.

Example of an alt-tag field:

class FormComponent extends React.Component {
  _getValue () {
    const { bindInput, element } = this.props
    return bindInput(element.name) // see @lefapps/forms
  }
  _modifyModel (action, { index, model, direction }) {
    let { name, value, onChange, multiple } = this._getValue()
    switch (action) {
      case 'create':
        multiple ? value.splice(value.length, 0, model) : (value = model)
        break
      case 'update':
        multiple ? value.splice(index, 1, model) : (value = model)
        break
      case 'delete':
        if (confirm('Are you sure you want to delete this file?')) {
          multiple ? value.splice(index, 1) : (value = undefined)
        }
        break
      case 'move':
        if (multiple) {
          value.splice(index, 1)
          value.splice(index + direction, 0, model)
        }
        break
    }
    this.setState({ value })
    return value
  }
  metaFields (isImage) {
    return isImage ? [{ name: 'alt', type: 'text' }] : []
  }
  metaFieldBuilder (value, index) {
    return ({ name, type }, key) => (
      <Input
        key={key}
        type={type}
        defaultValue={value[name]}
        placeholder={name}
        onChange={({ target }) =>
          this._modifyModel('update', {
            index,
            model: Object.assign(value, {
              [name]: target.value
            })
          })
        }
      />
    )
  }
  render () {
    return
      <Uploader
        onChange={model => this._modifyModel('create', { model })}
        {...config}
      >
        {values.map((value, index) =>
          <Preview
            {...value}
            key={index}
            extras={this.metaFields(value.isImage).map(this.metaFieldBuilder(value, index))}
          />
        )}
      </Uploader>
  }
}
0.0.7

2 years ago

0.0.6

3 years ago

0.0.5

4 years ago

0.0.4

4 years ago

0.0.3

4 years ago

0.0.2

5 years ago

0.0.1

5 years ago