diff src/uploadcare/uploadcare.js @ 0:8f4df159f06b

start public repo
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 11 Jul 2025 20:57:49 -0600
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/uploadcare/uploadcare.js	Fri Jul 11 20:57:49 2025 -0600
@@ -0,0 +1,267 @@
+'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();
+	};
+}