pics/public/js/crop_editor.js

242 lines
8.3 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.naturalHeight / 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 drag_target = this.image_container;
drag_target.addEventListener('mousedown', this.dragStart);
drag_target.addEventListener('mousemove', this.drag);
drag_target.addEventListener('mouseup', this.dragEnd);
drag_target.addEventListener('mouseout', this.dragEnd);
this.original_image.addEventListener('mousedown', event => {return false});
}
dragStart(event) {
this.isDragging = true;
console.log(`Start @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`);
}
dragEnd(event) {
if (!this.isDragging) {
return;
}
this.isDragging = false;
console.log(`End @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`);
}
drag(event) {
if (!this.isDragging) {
return;
}
console.log(`Drag @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`);
}
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 source_x = parseInt(this.source_x.value),
source_y = parseInt(this.source_y.value),
crop_width = parseInt(this.crop_width.value),
crop_height = parseInt(this.crop_height.value),
real_width = this.original_image.naturalWidth,
real_height = this.original_image.naturalHeight,
scaled_width = this.original_image.clientWidth,
scaled_height = this.original_image.clientHeight;
let width_scale = scaled_width / real_width,
height_scale = scaled_height / real_height;
crop_boundary.style.left = (this.source_x.value) * width_scale + "px";
crop_boundary.style.top = (this.source_y.value) * height_scale + "px";
crop_boundary.style.width = (this.crop_width.value) * width_scale + "px";
crop_boundary.style.height = (this.crop_height.value) * height_scale + "px";
}
}