view src/uploadcare/uploadcare.js @ 2:e32e4120dc70

minor
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 11 Jul 2025 21:23:39 -0600
parents 8f4df159f06b
children
line wrap: on
line source

'use strict';

let uploadcare = {};

/*

to set an option:
uploadcare[option] = value

from https://uploadcare.com/docs/uploads/file-uploader-options/
publicKey, imagesOnly, doNotStore

onError - function called with request for AJAX errors
maxFileSize - maximum file size of images in bytes

*/

uploadcare.maxDim = 2000;
uploadcare.processingImage = '/uploadcare/processing.gif';
uploadcare.cropprOptions = null;

function logToServer(msg) {
	ajax( '/log_info.js', 'msg='+encodeURIComponent(msg) );
}

{
	// compression

	function infoAddUrl(info) {
		if( info.url )
			return;
		return new Promise( function(resolve) {
			let reader = new FileReader();
			reader.onload = function() {
				info.url = reader.result;
				resolve();
			};
			reader.readAsDataURL(info.file);
		} );
	}
	uploadcare.infoAddUrl = infoAddUrl;

	let croppr;
	let dialog = null;

	async function infoAddCroppedImage(info) {
		if( !dialog ) {
			let html = `
				<dialog croppr>
					<img>
					<div buttons>
						<button save>Save</button>
						<button cancel>Cancel</button>
					</div>
				</dialog>
`			;
			document.body.insertAdjacentHTML( 'beforeend', html );
			dialog = document.querySelector('dialog[croppr]');
			dialog.onclose = function() {
				croppr.destroy();
			};
		}
		await infoAddUrl(info);
		let image = dialog.querySelector('img');
		image.src = info.url;
		return new Promise( function(resolve) {
			dialog.querySelector('button[save]').onclick = function() {
				info.image = image;
				info.crop = croppr.getValue();
				dialog.close();
				resolve();
			};
			dialog.querySelector('button[cancel]').onclick = function() {
				info.canceled = true;
				dialog.close();
				resolve();
			};
			image.onload = function() {
				dialog.showModal();
				croppr = new Croppr( image, uploadcare.cropprOptions );
			};
		} );
	}

	let supportsDialog = typeof HTMLDialogElement === 'function';

	async function infoAddImage(info) {
		if( info.image )
			return;
		if( uploadcare.cropprOptions && supportsDialog )
			return infoAddCroppedImage(info);
		await infoAddUrl(info);
		return new Promise( function(resolve) {
			let image = new Image();
			info.image = image;
			image.src = info.url;
			image.onload = function() {
				resolve();
			};
		} );
	}

	async function infoAddCanvas(info,maxDim) {
		await infoAddImage(info);
		if( info.canceled )
			return;
		let image = info.image;
		let width, height;
		let crop = info.crop;
		if( crop ) {
			width = crop.width;
			height = crop.height;
		} else {
			width = image.width;
			height = image.height;
		}
		if( maxDim ) {
			if( width > height ) {
				if( width > maxDim ) {
					height *= maxDim / width;
					width = maxDim;
				}
			} else {
				if( height > maxDim ) {
					width *= maxDim / height;
					height = maxDim;
				}
			}
		}
		let canvas = document.createElement('canvas');
		canvas.width = width;
		canvas.height = height;
		let ctx = canvas.getContext('2d');
		if( crop ) {
			ctx.drawImage( image, crop.x, crop.y, crop.width, crop.height, 0, 0, width, height );
		} else {
			ctx.drawImage( image, 0, 0, width, height );
		}
		info.canvas = canvas;
	}

	async function infoAddBlob(info,maxDim) {
		await infoAddCanvas(info,maxDim);
		if( info.canceled )
			return;
		return new Promise( function(resolve) {
			function done(blob) {
				info.blob = blob;
				resolve();
			}
			info.canvas.toBlob( done, 'image/jpeg' );
		} );
	}

	async function infoCompress(info) {
		let file = info.file;
		await infoAddBlob(info);
		if( info.canceled )
			return;
		let maxFileSize = uploadcare.maxFileSize;
		if( !info.blob || maxFileSize && info.blob.size > maxFileSize ) {
			await infoAddBlob(info,uploadcare.maxDim);
			if( !info.blob )  throw 'no blob';
		}
		info.compressed = info.blob;
		info.compressedName = file.name.replace( /(\.[^.]*)?$/, '.jpeg' );
	}
	uploadcare.infoCompress = infoCompress;


	// uploading

	function onload(url,onSuccess,onError,count) {
		count = count || 1;
		let request = new XMLHttpRequest();
		request.open( 'GET', url );
		request.onload = function() {
			if( request.status === 200 ) {
				onSuccess();
			} else if( request.status === 404 ) {
				console.log('onload failed '+count);
				if( count >= 20 ) {
					let text = 'Failed to get image after ' + count + ' tries, please try again';
					onError( request.status, text );
					return;
				}
				setTimeout( function() {
					onload(url,onSuccess,onError,count+1);
				}, 1000 );
			} else {
				onError( request.status, request.responseText) ;
			}
		};
		request.send();
	}

	function call(file,filename,callback,onError) {
		let request = new XMLHttpRequest();
		let url = 'https://upload.uploadcare.com/base/';
		request.open( 'POST', url );
		request.onload = function() {
			if( request.status !== 200 ) {
				onError( request.status, request.responseText );
				return;
			}
			let response = JSON.parse(request.responseText);
			let uuid = response.file;
			let url = 'https://ucarecdn.com/' + uuid + '/';
			function onSuccess() {
				callback( uuid, file.name );
			}
			onload( url, onSuccess, onError );
		};
		let formData = new FormData();
		formData.append( 'UPLOADCARE_PUB_KEY', uploadcare.publicKey );
		formData.append( 'UPLOADCARE_STORE', uploadcare.doNotStore ? '0' : '1' );
		formData.append( 'file', file, filename );
		request.send(formData);
	}

	let input = null;
	let img = null;

	function showImg() {
		img.style.display = 'block';
	}

	function hideImg() {
		img.style.display = 'none';
	}

	uploadcare.upload = function(callback) {
		if( !uploadcare.publicKey )
			throw new Error('uploadcare.publicKey required');
		if( !input ) {
			let html = `
				<input uploadcare type=file>
				<img uploadcare src="${uploadcare.processingImage}">
`			;
			document.body.insertAdjacentHTML( 'beforeend', html );
			input = document.querySelector('input[uploadcare][type="file"]');
			img = document.querySelector('img[uploadcare]');
		}
		input.accept = uploadcare.imagesOnly ? 'image/*' : '';
		input.onchange = async function() {
			function onError(status,text) {
				hideImg();
				if( text )
					alert(text);
				if( uploadcare.onError )
					uploadcare.onError(status,text);
			}
			function callback2(uuid,filename) {
				hideImg();
				callback(uuid,filename)
			}
			let info = { file: input.files[0] };
			input.value = null;
			await infoCompress(info);
			if( !info.canceled ) {
				showImg();
				call(info.compressed,info.compressedName,callback2,onError);
			}
		};
		input.click();
	};
}