forked from Public/pics
		
	
		
			
				
	
	
		
			304 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
class CropEditor {
 | 
						|
	constructor(opt) {
 | 
						|
		this.opt = opt;
 | 
						|
 | 
						|
		this.edit_crop_button = document.createElement("span");
 | 
						|
		this.edit_crop_button.className = "btn";
 | 
						|
		this.edit_crop_button.textContent = "Edit crop";
 | 
						|
		this.edit_crop_button.addEventListener('click', this.show.bind(this));
 | 
						|
 | 
						|
		this.thumbnail_select = document.getElementById(opt.thumbnail_select_id);
 | 
						|
		this.thumbnail_select.addEventListener('change', this.toggleCropButton.bind(this));
 | 
						|
		this.thumbnail_select.parentNode.insertBefore(this.edit_crop_button, this.thumbnail_select.nextSibling);
 | 
						|
 | 
						|
		this.toggleCropButton();
 | 
						|
	}
 | 
						|
 | 
						|
	initDOM() {
 | 
						|
		this.container = document.createElement("div");
 | 
						|
		this.container.id = "crop_editor";
 | 
						|
 | 
						|
		this.initPositionForm();
 | 
						|
		this.initImageContainer();
 | 
						|
 | 
						|
		this.parent = document.getElementById(this.opt.editor_container_parent_id);
 | 
						|
		this.parent.appendChild(this.container);
 | 
						|
	}
 | 
						|
 | 
						|
	initPositionForm() {
 | 
						|
		this.position = document.createElement("fieldset");
 | 
						|
		this.position.className = "crop_position";
 | 
						|
		this.container.appendChild(this.position);
 | 
						|
 | 
						|
		let source_x_label = document.createTextNode("Source X:");
 | 
						|
		this.position.appendChild(source_x_label);
 | 
						|
 | 
						|
		this.source_x = document.createElement("input");
 | 
						|
		this.source_x.addEventListener("keyup", this.positionBoundary.bind(this));
 | 
						|
		this.position.appendChild(this.source_x);
 | 
						|
 | 
						|
		let source_y_label = document.createTextNode("Source Y:");
 | 
						|
		this.position.appendChild(source_y_label);
 | 
						|
 | 
						|
		this.source_y = document.createElement("input");
 | 
						|
		this.source_y.addEventListener("keyup", this.positionBoundary.bind(this));
 | 
						|
		this.position.appendChild(this.source_y);
 | 
						|
 | 
						|
		let crop_width_label = document.createTextNode("Crop width:");
 | 
						|
		this.position.appendChild(crop_width_label);
 | 
						|
 | 
						|
		this.crop_width = document.createElement("input");
 | 
						|
		this.crop_width.addEventListener("keyup", this.positionBoundary.bind(this));
 | 
						|
		this.position.appendChild(this.crop_width);
 | 
						|
 | 
						|
		let crop_height_label = document.createTextNode("Crop height:");
 | 
						|
		this.position.appendChild(crop_height_label);
 | 
						|
 | 
						|
		this.crop_height = document.createElement("input");
 | 
						|
		this.crop_height.addEventListener("keyup", this.positionBoundary.bind(this));
 | 
						|
		this.position.appendChild(this.crop_height);
 | 
						|
 | 
						|
		this.save_button = document.createElement("span");
 | 
						|
		this.save_button.className = "btn";
 | 
						|
		this.save_button.textContent = "Save";
 | 
						|
		this.save_button.addEventListener('click', this.save.bind(this));
 | 
						|
		this.position.appendChild(this.save_button);
 | 
						|
 | 
						|
		this.abort_button = document.createElement("span");
 | 
						|
		this.abort_button.className = "btn btn-red";
 | 
						|
		this.abort_button.textContent = "Abort";
 | 
						|
		this.abort_button.addEventListener('click', this.hide.bind(this));
 | 
						|
		this.position.appendChild(this.abort_button);
 | 
						|
	}
 | 
						|
 | 
						|
	initImageContainer() {
 | 
						|
		this.image_container = document.createElement("div");
 | 
						|
		this.image_container.className = "crop_image_container";
 | 
						|
		this.container.appendChild(this.image_container);
 | 
						|
 | 
						|
		this.crop_boundary = document.createElement("div");
 | 
						|
		this.crop_boundary.id = "crop_boundary";
 | 
						|
		this.image_container.appendChild(this.crop_boundary);
 | 
						|
 | 
						|
		this.original_image = document.createElement("img");
 | 
						|
		this.original_image.draggable = false;
 | 
						|
		this.original_image.id = "original_image";
 | 
						|
		this.original_image.src = this.opt.original_image_src;
 | 
						|
		this.image_container.appendChild(this.original_image);
 | 
						|
	}
 | 
						|
 | 
						|
	setDefaultCrop(cropAspectRatio, cropMethod) {
 | 
						|
		let source = this.original_image;
 | 
						|
		let sourceAspectRatio = source.naturalWidth / source.naturalHeight;
 | 
						|
 | 
						|
		// Cropping from the centre?
 | 
						|
		if (cropMethod === "c") {
 | 
						|
			// Crop vertically from the centre, using the entire width.
 | 
						|
			if (sourceAspectRatio <= cropAspectRatio) {
 | 
						|
				this.crop_width.value = source.naturalWidth;
 | 
						|
				this.crop_height.value = Math.ceil(source.naturalWidth / cropAspectRatio);
 | 
						|
				this.source_x.value = 0;
 | 
						|
				this.source_y.value = Math.ceil((source.naturalHeight - this.crop_height.value) / 2);
 | 
						|
			}
 | 
						|
			// Crop horizontally from the centre, using the entire height.
 | 
						|
			else {
 | 
						|
				this.crop_width.value = Math.ceil(cropAspectRatio * source.naturalHeight);
 | 
						|
				this.crop_height.value = source.naturalHeight;
 | 
						|
				this.source_x.value = Math.ceil((source.naturalWidth - this.crop_width.value) / 2);
 | 
						|
				this.source_y.value = 0;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// Cropping a top or bottom slice?
 | 
						|
		else {
 | 
						|
			// Can we actually take a top or bottom slice from the original image?
 | 
						|
			if (sourceAspectRatio <= cropAspectRatio) {
 | 
						|
				this.crop_width.value = source.naturalWidth;
 | 
						|
				this.crop_height.value = Math.floor(source.naturalWidth / cropAspectRatio);
 | 
						|
				this.source_x.value = "0";
 | 
						|
				this.source_y.value = cropMethod.indexOf("t") !== -1 ? "0" : source.naturalHeight - this.crop_height.value;
 | 
						|
			}
 | 
						|
			// Otherwise, take a vertical slice from the centre.
 | 
						|
			else {
 | 
						|
				this.crop_width.value = Math.floor(source.naturalHeight * cropAspectRatio);
 | 
						|
				this.crop_height.value = source.naturalHeight;
 | 
						|
				this.source_x.value = Math.floor((source.naturalWidth - this.crop_width.value) / 2);
 | 
						|
				this.source_y.value = "0";
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	setPositionFormValues() {
 | 
						|
		let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
 | 
						|
 | 
						|
		if (typeof current.crop_region === "undefined") {
 | 
						|
			let aspectRatio = current.crop_width / current.crop_height;
 | 
						|
			this.setDefaultCrop(aspectRatio, current.crop_method);
 | 
						|
		} else {
 | 
						|
			let region = current.crop_region.split(',');
 | 
						|
			this.crop_width.value = region[0];
 | 
						|
			this.crop_height.value = region[1];
 | 
						|
			this.source_x.value = region[2];
 | 
						|
			this.source_y.value = region[3];
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	showContainer() {
 | 
						|
		this.container.style.display = '';
 | 
						|
		this.setPositionFormValues();
 | 
						|
		this.positionBoundary();
 | 
						|
		this.addEvents();
 | 
						|
	}
 | 
						|
 | 
						|
	save() {
 | 
						|
		let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
 | 
						|
		let payload = {
 | 
						|
			thumb_width: current.crop_width,
 | 
						|
			thumb_height: current.crop_height,
 | 
						|
			crop_method: current.crop_method,
 | 
						|
			crop_width: this.crop_width.value,
 | 
						|
			crop_height: this.crop_height.value,
 | 
						|
			source_x: this.source_x.value,
 | 
						|
			source_y: this.source_y.value
 | 
						|
		};
 | 
						|
		let req = HttpRequest("post", this.opt.submitUrl + "?id=" + this.opt.asset_id + "&updatethumb",
 | 
						|
			"data=" + encodeURIComponent(JSON.stringify(payload)), function(response) {
 | 
						|
				this.opt.after_save(response);
 | 
						|
				this.hide();
 | 
						|
			}.bind(this));
 | 
						|
	}
 | 
						|
 | 
						|
	show() {
 | 
						|
		if (typeof this.container === "undefined") {
 | 
						|
			this.initDOM();
 | 
						|
		}
 | 
						|
 | 
						|
		// Defer showing and positioning until image is loaded.
 | 
						|
		// !!! TODO: add a spinner in the mean time?
 | 
						|
		if (this.original_image.naturalWidth > 0) {
 | 
						|
			this.showContainer();
 | 
						|
		} else {
 | 
						|
			this.original_image.addEventListener("load", event => this.showContainer());
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	hide() {
 | 
						|
		this.container.style.display = "none";
 | 
						|
	}
 | 
						|
 | 
						|
	addEvents(event) {
 | 
						|
		let cropTarget = this.image_container;
 | 
						|
		cropTarget.addEventListener('mousedown', this.cropSelectionStart.bind(this));
 | 
						|
		cropTarget.addEventListener('mousemove', this.cropSelection.bind(this));
 | 
						|
		cropTarget.addEventListener('mouseup', this.cropSelectionEnd.bind(this));
 | 
						|
		// cropTarget.addEventListener('mouseout', this.cropSelectionEnd.bind(this));
 | 
						|
 | 
						|
		this.original_image.addEventListener('mousedown', event => {return false});
 | 
						|
		this.original_image.addEventListener('dragstart', event => {return false});
 | 
						|
 | 
						|
		let moveTarget = this.crop_boundary;
 | 
						|
		moveTarget.addEventListener('mousedown', this.moveSelectionStart.bind(this));
 | 
						|
		moveTarget.addEventListener('mousemove', this.moveSelection.bind(this));
 | 
						|
		moveTarget.addEventListener('mouseup', this.moveSelectionEnd.bind(this));
 | 
						|
	}
 | 
						|
 | 
						|
	cropSelectionStart(event) {
 | 
						|
		if (this.isMoving) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		this.isDragging = true;
 | 
						|
		this.dragStartX = event.x - this.image_container.offsetLeft;
 | 
						|
		this.dragStartY = event.y - this.image_container.offsetTop;
 | 
						|
	}
 | 
						|
 | 
						|
	cropSelectionEnd(event) {
 | 
						|
		this.isDragging = false;
 | 
						|
		this.handleCropSelectionEvent(event);
 | 
						|
	}
 | 
						|
 | 
						|
	cropSelection(event) {
 | 
						|
		this.handleCropSelectionEvent(event);
 | 
						|
	}
 | 
						|
 | 
						|
	getScaleFactor() {
 | 
						|
		return this.original_image.naturalWidth / this.original_image.clientWidth;
 | 
						|
	}
 | 
						|
 | 
						|
	handleCropSelectionEvent(event) {
 | 
						|
		if (!this.isDragging) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		this.dragEndX = event.x - this.image_container.offsetLeft;
 | 
						|
		this.dragEndY = event.y - this.image_container.offsetTop;
 | 
						|
 | 
						|
		let scaleFactor = this.getScaleFactor();
 | 
						|
 | 
						|
		this.source_x.value = Math.ceil(Math.min(this.dragStartX, this.dragEndX) * scaleFactor);
 | 
						|
		this.source_y.value = Math.ceil(Math.min(this.dragStartY, this.dragEndY) * scaleFactor);
 | 
						|
 | 
						|
		let width = Math.ceil(Math.abs(this.dragEndX - this.dragStartX) * scaleFactor);
 | 
						|
		this.crop_width.value = Math.min(width, this.original_image.naturalWidth - this.source_x.value);
 | 
						|
 | 
						|
		let height = Math.ceil(Math.abs(this.dragEndY - this.dragStartY) * scaleFactor);
 | 
						|
		this.crop_height.value = Math.min(height, this.original_image.naturalHeight - this.source_y.value);
 | 
						|
 | 
						|
		this.positionBoundary();
 | 
						|
	}
 | 
						|
 | 
						|
	handleCropMoveEvent(event) {
 | 
						|
		if (!this.isMoving) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		this.dragEndX = event.x - this.crop_boundary.offsetLeft;
 | 
						|
		this.dragEndY = event.y - this.crop_boundary.offsetTop;
 | 
						|
 | 
						|
		let scaleFactor = this.getScaleFactor();
 | 
						|
 | 
						|
		let x = parseInt(this.source_x.value) + Math.ceil((this.dragEndX - this.dragStartX) * scaleFactor);
 | 
						|
		if (x + parseInt(this.crop_width.value) > this.original_image.naturalWidth) {
 | 
						|
			x += this.original_image.naturalWidth - (x + parseInt(this.crop_width.value));
 | 
						|
		}
 | 
						|
		this.source_x.value = Math.max(x, 0);
 | 
						|
 | 
						|
		let y = parseInt(this.source_y.value) + Math.ceil((this.dragEndY - this.dragStartY) * scaleFactor);
 | 
						|
		if (y + parseInt(this.crop_height.value) > this.original_image.naturalHeight) {
 | 
						|
			y += this.original_image.naturalHeight - (y + parseInt(this.crop_height.value));
 | 
						|
		}
 | 
						|
		this.source_y.value = Math.max(y, 0);
 | 
						|
 | 
						|
		this.positionBoundary();
 | 
						|
	}
 | 
						|
 | 
						|
	moveSelectionStart(event) {
 | 
						|
		if (this.isDragging) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		this.isMoving = true;
 | 
						|
		this.dragStartX = event.x - this.crop_boundary.offsetLeft;
 | 
						|
		this.dragStartY = event.y - this.crop_boundary.offsetTop;
 | 
						|
	}
 | 
						|
 | 
						|
	moveSelectionEnd(event) {
 | 
						|
		this.isMoving = false;
 | 
						|
		this.handleCropMoveEvent(event);
 | 
						|
	}
 | 
						|
 | 
						|
	moveSelection(event) {
 | 
						|
		this.handleCropMoveEvent(event);
 | 
						|
	}
 | 
						|
 | 
						|
	toggleCropButton() {
 | 
						|
		let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
 | 
						|
		this.edit_crop_button.style.display = typeof current.crop_method === "undefined" ? "none" : "";
 | 
						|
	}
 | 
						|
 | 
						|
	positionBoundary(event) {
 | 
						|
		let scaleFactor = this.getScaleFactor();
 | 
						|
		crop_boundary.style.left = parseInt(this.source_x.value) / scaleFactor + "px";
 | 
						|
		crop_boundary.style.top = parseInt(this.source_y.value) / scaleFactor + "px";
 | 
						|
		crop_boundary.style.width = parseInt(this.crop_width.value) / scaleFactor + "px";
 | 
						|
		crop_boundary.style.height = parseInt(this.crop_height.value) / scaleFactor + "px";
 | 
						|
	}
 | 
						|
}
 |