npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@lefapps/uploader

v0.0.7

Published

React package for translations and cms

Downloads

13

Readme

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

| Prop | Type | RequiredDefault | Description | | ----------- | ------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | label | StringComponent | ✗ | Label to display above the file input | | placeholder | String | ✗ | Label to display inside the file input | | name | String | ✓ | Name attribute of the file input element | | multiple | Bool | false | Allow multiple files to be selected for upload | | uploader | ObjectClass | ✓ | Logic to send the file to your storage of choiceSee below for specific information. Works seamlessly with edgee:slingshot directives. | | sizes | [Object] | ✗ | Thumbnail configuration for image files | | invalid | Bool | ✗ | Whether the input is valid or not | | id | String | ✗ | Id attribute for DOM reference | | className | String | ✗ | Class attribute for custom styling | | metaData | Object | ✗ | Optional 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.

| Key | Type | RequiredDefault | Description | | ------- | ------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | label | String | ✓ | Prefix for this thumbnail, easy to reference laterE.g. square-sm, banner-lg, … | | width | Integer | ✓ | Width of thumbnail | | height | Integer | ✓ | Height of thumbnail | | crop | Bool | false | Whether 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. | | quality | Number | 60 | Image quality between .25 and 1 or 25 and 100 %Numbers greater than 100 set the quality to maximum (100 %). | | retina | Bool | false | Create 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.

| Key | Type | Description | | ------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | name | String | Unique filename for all uploaded files | | url | String | success from the send callback, most likely the full url to the uploaded original | | local | Blob | use 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. | | isImage | Bool | Whether the uploaded file was seen as an image | | error | String | If 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

| Prop | Type | RequiredDefault | Description | | -------- | --------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- | | name | String | '' | Name of the file | | url | String | '' | Url of preview (image)If no preview is available, the file type will be shown instead. | | local | String | '' | Urlstring of the local image, overrides url if present | | error | String | '' | Display an error message instead of the preview | | children | Component | ✗ | Extend preview functionality to remove, sort, … | | extras | Component | ✗ | Optional 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>
  }
}