├── .gitignore ├── LICENSE ├── README.md ├── lib └── react-cloudinary-uploader.js ├── package.json ├── public └── index.html └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Domenico Solazzo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-cloudinary-uploader 2 | React File Uploader for Cloudinary. 3 | This React component wraps the Cloudinary Widget.It wraps all the properties of both the widget and the returned results in the state of the React component. 4 | 5 | # Installation 6 | ============== 7 | You need to [include the Cloudinary script](http://goo.gl/VnTE0q) at the bottom of your page, but before loading your React app. 8 | 9 | * npm install 10 | * node server.js 11 | 12 | # Configuration 13 | =============== 14 | The component accepts few properties as input. cloudName and uploadPreset are required properties for this component. 15 | * **cloudName**: the cloud name that you can find in your configuration in Cloudinary. 16 | * **uploadPreset**: The upload_preset that you can find in your settins (Upload) in Cloudinary. 17 | * **showPoweredBy** [true | false]: It shows the poweredBy logo in the widget. 18 | * **allowedFormats**: An array of allowed format. (e.g. ['jpeg', 'png']) 19 | * **maxFileSize**: If specified, perform client side validation that prevents uploading files bigger than the given bytes size (e.g. 130000) 20 | * **maxImageWidth**: If specified, client-side scale-down resizing takes place before uploading if the width of the selected file is bigger than the specified value. (e.g. 2000) 21 | * **maxImageHeight**: If specified, client-side scale-down resizing takes place before uploading if the height of the selected file is bigger than the specified value. (e.g. 2000) 22 | * **sources** ["local", "web", "web"]: List of file sources 23 | * **defaultSource**: The default selected source tab when the widget is opened. 24 | * **multiple**: Whether selecting and uploading multiple images is allowed. 25 | * **maxFiles**: The maximum number of files allowed in multiple upload mode. 26 | * **cropping** ["server" | null]: Whether to enable interactive cropping of images before uploading. 27 | * **croppingAspectRatio**: If specified, enforce the given aspect ratio on selected region when performing interactive cropping. (e.g. 0.5) 28 | * **publicId**: Custom public ID to assign to a single uploaded image. 29 | * **folder**: Folder name for all uploaded images. Acts as the prefix of assigned public IDs. 30 | * **tags**: One or more tags to assign to the uploaded images. 31 | * **resourceType** ["auto", "image", "raw"]: The resource type of the uploaded files. 32 | * **contextAlt**: Additional context metadata to attach to the uploaded images. 33 | * **contextCaption**: Additional context metadata to attach to the uploaded images. 34 | * **buttonClass**: Allows overriding the default CSS class name of the upload button added to your site. 35 | * **buttonCaption**: Allows overriding the default caption of the upload button added to your site. 36 | 37 | # State 38 | ======= 39 | The React component keeps track of the information returned from the upload. 40 | * **bytes**: Bytes of the image 41 | * **created_at**: Last modification date of the image 42 | * **etag**: Etag of the image 43 | * **format**: Format of the image 44 | * **height**: Height of the image 45 | * **width**: Width of the image, 46 | * **path**: Image path, 47 | * **public_id**: Image public id, 48 | * **resource_type**: Resource type, 49 | * **secure_url**: Secure URL for the image, 50 | * **signature**: Signature for the image, 51 | * **tags**: List of tags connected to the image 52 | * **thumbnail_url**: Thumbnail url 53 | * **type**: Image type 54 | * **url**: Image URL 55 | * **version**: Version of the image 56 | * **isError**: True if there was an error during upload, false otherwise 57 | * **errorMessage**: The error message in case there was an error 58 | * **uuid**: Unique identifier for the widget 59 | 60 | ## WIP 61 | * Clean up some state properties 62 | * Allow multiple upload 63 | * Allow basic transformations 64 | -------------------------------------------------------------------------------- /lib/react-cloudinary-uploader.js: -------------------------------------------------------------------------------- 1 | 2 | var ReactCloudinaryUploader = React.createClass({ 3 | propTypes:{ 4 | cloudName: React.PropTypes.string.isRequired, 5 | uploadPreset: React.PropTypes.string.isRequired, 6 | showPoweredBy: React.PropTypes.bool, 7 | allowedFormats: React.PropTypes.array, 8 | maxFileSize: React.PropTypes.number, 9 | maxImageWidth: React.PropTypes.number, 10 | maxImageHeight: React.PropTypes.number, 11 | sources: React.PropTypes.arrayOf(React.PropTypes.string), 12 | defaultSource: React.PropTypes.string, 13 | multiple: React.PropTypes.bool, 14 | maxFiles: React.PropTypes.number, 15 | cropping: React.PropTypes.string, 16 | croppingAspectRatio: React.PropTypes.number, 17 | publicId: React.PropTypes.string, 18 | folder: React.PropTypes.string, 19 | tags: React.PropTypes.arrayOf(React.PropTypes.string), 20 | resourceType: React.PropTypes.string, 21 | contextAlt: React.PropTypes.string, 22 | contextCaption: React.PropTypes.string, 23 | buttonClass: React.PropTypes.string, 24 | buttonCaption: React.PropTypes.string 25 | }, 26 | getDefaultProps: function(){ 27 | return { 28 | showPoweredBy: false, 29 | sources: ['local', 'url', 'camera'], 30 | defaultSource: 'local', 31 | multiple: false, 32 | maxFiles: null, 33 | cropping: null, 34 | croppingAspectRation: null, 35 | publicId: null, 36 | folder: null, 37 | tags: null, 38 | resourceType: 'auto', 39 | contextAlt: null, 40 | contextCaption: null, 41 | allowedFormats: ['png', 'gif', 'jpeg'], 42 | maxFileSize: null, 43 | maxImageWidth: null, 44 | maxImageHeight: null, 45 | buttonClass: 'cloudinary-button', 46 | buttonCaption: 'Upload image' 47 | } 48 | }, 49 | getInitialState: function(){ 50 | var initialState = { 51 | cloudName: this.props.cloudName, 52 | uploadPreset: this.props.uploadPreset, 53 | bytes: null, 54 | created_at: null, 55 | etag: null, 56 | format: null, 57 | height: null, 58 | width: null, 59 | path: null, 60 | public_id: null, 61 | resource_type: null, 62 | secure_url: null, 63 | signature: null, 64 | tags: [], 65 | thumbnail_url: null, 66 | type:null, 67 | url: null, 68 | version: null, 69 | isError: false, 70 | errorMessage: null, 71 | uuid: undefined, 72 | showPoweredBy: false, 73 | allowedFormats: null, 74 | uuid: this.uuid() 75 | }; 76 | return initialState; 77 | }, 78 | uuid: function(){ 79 | function guid() { 80 | function s4() { 81 | return Math.floor((1 + Math.random()) * 0x10000) 82 | .toString(16) 83 | .substring(1); 84 | } 85 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 86 | s4() + '-' + s4() + s4() + s4(); 87 | } 88 | return guid(); 89 | }, 90 | getUploadOptions: function(){ 91 | var options = { 92 | cloud_name: this.state.cloudName, 93 | upload_preset: this.state.uploadPreset 94 | }; 95 | options.sources = this.props.sources; 96 | options.multiple = this.props.multiple; 97 | 98 | if(this.props.maxFiles){ 99 | options.max_files = this.props.maxFiles 100 | } 101 | 102 | if(this.props.cropping && this.props.cropping === 'server'){ 103 | options.cropping = this.props.cropping; 104 | } 105 | 106 | if(this.croppingAspectRatio){ 107 | options.cropping_aspect_ratio = this.props.croppingAspectRatio; 108 | } 109 | 110 | if(this.props.publicId){ 111 | options.public_id = this.props.public_id; 112 | } 113 | 114 | if(this.props.folder){ 115 | options.folder = this.props.folder; 116 | } 117 | 118 | if(this.props.tags && this.props.tags.length > 0){ 119 | options.tags = this.props.tags; 120 | } 121 | 122 | if(this.props.resourceType){ 123 | options.resourceType = this.props.resourceType; 124 | } 125 | 126 | if(this.props.allowedFormats){ 127 | options.allowedFormats = this.props.allowedFormats 128 | } 129 | 130 | var context = {}; 131 | if(this.props.contextAlt){ 132 | context.alt = this.props.contextAlt; 133 | } 134 | 135 | if(this.props.contextCaption){ 136 | context.caption = this.props.contextCaption; 137 | } 138 | 139 | if(Object.keys(context).length > 0){ 140 | options.context = context; 141 | } 142 | 143 | return options; 144 | }, 145 | setError: function(isError, errorMessage){ 146 | self.setState({ 147 | isError: true, 148 | errorMessage: 'No result returned from Cloudinary' 149 | }); 150 | }, 151 | setUploadResult: function(uploadedImage){ 152 | this.setState({ 153 | bytes: uploadedImage.bytes, 154 | createdAt: uploadedImage.created_at, 155 | etag: uploadedImage.etag, 156 | format: uploadedImage.format, 157 | height: uploadedImage.height, 158 | path: uploadedImage.path, 159 | publicId: uploadedImage.public_id, 160 | resourceType: uploadedImage.resource_type, 161 | secureUrl: uploadedImage.secure_url, 162 | signature: uploadedImage.signature, 163 | tags: uploadedImage.tags, 164 | thumbnailUrl: uploadedImage.thumbnail_url, 165 | type: uploadedImage.type, 166 | url: uploadedImage.url, 167 | version: uploadedImage.version, 168 | width: uploadedImage.width 169 | }); 170 | }, 171 | handleClick: function(ev){ 172 | self = this; 173 | try{ 174 | var options = this.getUploadOptions(); 175 | cloudinary.openUploadWidget( 176 | options, 177 | function(error, result) { 178 | if (error){ 179 | self.setError(true, error) 180 | return false; 181 | } 182 | 183 | if (!result || result.length === 0){ 184 | self.setError(true, 'No result from Cloudinary'); 185 | return false; 186 | } 187 | var uploadedImage = result[0]; 188 | self.setUploadResult(uploadedImage); 189 | return true; 190 | } 191 | ); 192 | }catch(e){ 193 | self.setError(true, e); 194 | return false; 195 | } 196 | 197 | }, 198 | render: function(){ 199 | var uploader_id = "uploader_" + this.state.uuid; 200 | var image = this.state.thumbnailUrl ? this.state.thumbnailUrl: '#'; 201 | return ( 202 |
203 |
204 | Put your alt message here 205 |
206 | {this.props.buttonCaption} 209 |
210 | ); 211 | } 212 | }); 213 | 214 | React.render( 215 | , 216 | document.getElementById('content') 217 | ); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-cloudinary-uploader", 3 | "version": "0.0.1", 4 | "description": "A React file uploader for Cloudinary", 5 | "dependencies":{ 6 | "body-parser":"^1.4.3", 7 | "express": "^4.12.2" 8 | }, 9 | "repository":{ 10 | "type": "git", 11 | "url": "https://github.com/domenicosolazzo/react-cloudinary-uploader" 12 | }, 13 | "keywords":[ 14 | "react", 15 | "fileuploader", 16 | "html5", 17 | "cloudinary", 18 | "base64" 19 | ], 20 | "author":"domenicosolazzo" 21 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Base64 uploader 5 | 6 | 7 | 8 | 9 |
A React File Uploader for Cloudinary
10 |
11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var express = require('express'); 4 | var bodyParser = require('body-parser'); 5 | var app = express(); 6 | 7 | app.use('/', express.static(path.join(__dirname, 'public'))); 8 | app.use('/lib', express.static(path.join(__dirname, 'lib'))); 9 | 10 | app.listen(3000); 11 | console.log('Server started http://localhost:3000'); 12 | --------------------------------------------------------------------------------