Make crop editor usable #22
@ -116,18 +116,25 @@ class EditAsset extends HTMLController
|
||||
if (!preg_match('~^(?<width>\d+)x(?<height>\d+)(?<suffix>_c(?<method>[best]?))?$~', $selector, $thumb))
|
||||
continue;
|
||||
|
||||
$has_crop_boundary = isset($metadata['crop_' . $thumb['width'] . 'x' . $thumb['height']]);
|
||||
$has_custom_image = isset($metadata['custom_' . $thumb['width'] . 'x' . $thumb['height']]);
|
||||
$dimensions = $thumb['width'] . 'x' . $thumb['height'];
|
||||
|
||||
// Does the thumbnail exist on disk? If not, use an url to generate it.
|
||||
Aaron marked this conversation as resolved
|
||||
if (!$filename || !file_exists(THUMBSDIR . '/' . $subdir . '/' . $filename))
|
||||
$thumb_url = BASEURL . '/thumbnail/' . $image->getId() . '/' . $dimensions . ($thumb['suffix'] ?? '') . '/';
|
||||
else
|
||||
$thumb_url = THUMBSURL . '/' . $subdir . '/' . $filename;
|
||||
|
||||
$has_crop_boundary = isset($metadata['crop_' . $dimensions]);
|
||||
$has_custom_image = isset($metadata['custom_' . $dimensions]);
|
||||
|
||||
$thumbs[] = [
|
||||
'dimensions' => [(int) $thumb['width'], (int) $thumb['height']],
|
||||
'cropped' => !$has_custom_image && (!empty($thumb['suffix']) || $has_crop_boundary),
|
||||
'crop_method' => !$has_custom_image && !empty($thumb['method']) ? $thumb['method'] : (!empty($thumb['suffix']) ? 'c' : null),
|
||||
'crop_region' => $has_crop_boundary ? $metadata['crop_' . $thumb['width'] . 'x' . $thumb['height']] : null,
|
||||
'crop_region' => $has_crop_boundary ? $metadata['crop_' . $dimensions] : null,
|
||||
'custom_image' => $has_custom_image,
|
||||
'filename' => $filename,
|
||||
'full_path' => THUMBSDIR . '/' . $subdir . '/' . $filename,
|
||||
'url' => THUMBSURL . '/' . $subdir . '/' . $filename,
|
||||
'status' => file_exists(THUMBSDIR . '/' . $subdir . '/' . $filename),
|
||||
'url' => $thumb_url,
|
||||
];
|
||||
}
|
||||
|
||||
@ -144,18 +151,19 @@ class EditAsset extends HTMLController
|
||||
$crop_value = $data->crop_width . ',' . $data->crop_height . ',' . $data->source_x . ',' . $data->source_y;
|
||||
$meta[$crop_key] = $crop_value;
|
||||
|
||||
// If we uploaded a custom thumbnail, stop considering it such.
|
||||
// If we previously uploaded a custom thumbnail, stop considering it such.
|
||||
$custom_key = 'custom_' . $data->thumb_width . 'x' . $data->thumb_height;
|
||||
if (isset($meta[$custom_key]))
|
||||
{
|
||||
// TODO: delete from disk
|
||||
Roflin
commented
Is this still needed? Is this still needed?
Aaron
commented
It ought to be done, but I'd like to refactor thumbnail file management in general so this kind of thing is not left to the individual controllers. It ought to be done, but I'd like to refactor thumbnail file management in general so this kind of thing is not left to the individual controllers.
|
||||
unset($meta[$custom_key]);
|
||||
}
|
||||
|
||||
// Save meta changes so far.
|
||||
$image->setMetaData($meta);
|
||||
|
||||
// Force a rebuild of related thumbnails.
|
||||
$thumb_key = 'thumb_' . $data->thumb_width . 'x' . $data->thumb_height;
|
||||
foreach ($meta as $meta_key => $meta_value)
|
||||
if ($meta_key === $thumb_key || strpos($meta_key, $thumb_key . '_') !== false)
|
||||
unset($meta[$meta_key]);
|
||||
|
||||
$image->setMetaData($meta);
|
||||
$image->removeThumbnailsOfSize($data->thumb_width, $data->thumb_height);
|
||||
|
||||
$payload = [
|
||||
'key' => $crop_key,
|
||||
|
@ -133,9 +133,9 @@ class Image extends Asset
|
||||
|
||||
public function removeAllThumbnails()
|
||||
{
|
||||
foreach ($this->thumbnails as $key => $value)
|
||||
foreach ($this->thumbnails as $key => $filename)
|
||||
{
|
||||
$thumb_path = THUMBSDIR . '/' . $this->subdir . '/' . $value;
|
||||
$thumb_path = THUMBSDIR . '/' . $this->subdir . '/' . $filename;
|
||||
if (is_file($thumb_path))
|
||||
unlink($thumb_path);
|
||||
}
|
||||
@ -146,6 +146,30 @@ class Image extends Asset
|
||||
['id_asset' => $this->id_asset]);
|
||||
}
|
||||
|
||||
public function removeThumbnailsOfSize($width, $height)
|
||||
{
|
||||
foreach ($this->thumbnails as $key => $filename)
|
||||
{
|
||||
if (strpos($key, $width . 'x' . $height) !== 0)
|
||||
continue;
|
||||
|
||||
$thumb_path = THUMBSDIR . '/' . $this->subdir . '/' . $filename;
|
||||
if (is_file($thumb_path))
|
||||
unlink($thumb_path);
|
||||
}
|
||||
|
||||
return Registry::get('db')->query('
|
||||
DELETE FROM assets_thumbs
|
||||
WHERE id_asset = {int:id_asset} AND
|
||||
width = {int:width} AND
|
||||
height = {int:height}',
|
||||
[
|
||||
'height' => $height,
|
||||
'id_asset' => $this->id_asset,
|
||||
'width' => $width,
|
||||
]);
|
||||
}
|
||||
|
||||
public function replaceThumbnail($descriptor, $tmp_file)
|
||||
{
|
||||
if (!is_file($tmp_file))
|
||||
|
@ -164,6 +164,8 @@ class Thumbnail
|
||||
$this->filename_suffix .= 's';
|
||||
elseif ($this->crop_mode === self::CROP_MODE_SLICE_BOTTOM)
|
||||
$this->filename_suffix .= 'b';
|
||||
elseif ($this->crop_mode === self::CROP_MODE_BOUNDARY)
|
||||
$this->filename_suffix .= 'e';
|
||||
}
|
||||
else
|
||||
$this->filename_suffix = '';
|
||||
|
@ -145,46 +145,55 @@ body {
|
||||
/* Crop editor
|
||||
----------------*/
|
||||
#crop_editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #000;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 100;
|
||||
color: #fff;
|
||||
}
|
||||
#crop_editor input {
|
||||
#crop_editor input[type=number] {
|
||||
width: 50px;
|
||||
background: #555;
|
||||
color: #fff;
|
||||
}
|
||||
.crop_image_container {
|
||||
position: relative;
|
||||
#crop_editor input[type=checkbox] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.crop_position {
|
||||
background: rgba(0, 0, 0, 1.0);
|
||||
border: none;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.crop_position input, .crop_position .btn {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.crop_image_container {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
max-height: calc(100% - 34px);
|
||||
}
|
||||
.crop_image_container img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
border: 1px solid #000;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 700px;
|
||||
}
|
||||
#crop_boundary {
|
||||
border: 1px solid rgba(255, 255, 255, 0.75);
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px dashed rgb(255, 255, 255);
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
cursor: move;
|
||||
position: absolute;
|
||||
z-index: 200;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
top: 400px;
|
||||
left: 300px;
|
||||
filter: invert(100%); /* temp */
|
||||
}
|
||||
|
||||
|
||||
|
@ -375,34 +375,59 @@ footer a {
|
||||
|
||||
input, select, .btn {
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 4px;
|
||||
color: #000;
|
||||
font: 13px/1.7 "Open Sans", "Helvetica", sans-serif;
|
||||
padding: 3px;
|
||||
}
|
||||
input[type=submit], button, .btn {
|
||||
background: #ddd;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #aaa;
|
||||
display: inline-block;
|
||||
font: inherit;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
input[type=submit]:hover, button:hover, .btn:hover {
|
||||
background-color: #d0d0d0;
|
||||
border-color: #a0a0a0;
|
||||
}
|
||||
textarea {
|
||||
border: 1px solid #ccc;
|
||||
font: 12px/1.4 'Monaco', 'Inconsolata', 'DejaVu Sans Mono', monospace;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 4px;
|
||||
font: 14px/1.4 'Inconsolata', 'DejaVu Sans Mono', monospace;
|
||||
padding: 0.75%;
|
||||
width: 98.5%;
|
||||
}
|
||||
|
||||
input[type=submit], button, .btn {
|
||||
background-color: #eee;
|
||||
border-color: #dbdbdb;
|
||||
border-width: 1px;
|
||||
border-radius: 4px;
|
||||
color: #363636;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
justify-content: center;
|
||||
padding-bottom: calc(0.4em - 1px);
|
||||
padding-left: 0.8em;
|
||||
padding-right: 0.8em;
|
||||
padding-top: calc(0.4em - 1px);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
input:hover, select:hover, button:hover, .btn:hover {
|
||||
border-color: #b5b5b5;
|
||||
}
|
||||
input:focus, select:focus, button:focus, .btn:focus {
|
||||
border-color: #3273dc;
|
||||
}
|
||||
input:focus:not(:active), select:focus:not(:active), button:focus:not(:active), .btn:focus:not(:active) {
|
||||
box-shadow: 0px 0px 0px 2px rgba(50, 115, 220, 0.25);
|
||||
}
|
||||
input:active, select:active, button:active, .btn:active {
|
||||
border-color: #4a4a4a;
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
background: #F3B076;
|
||||
border-color: #C98245;
|
||||
background: #eebbaa;
|
||||
border-color: #cc9988;
|
||||
}
|
||||
.btn-red:hover, .btn-red:focus {
|
||||
border-color: #bb7766;
|
||||
color: #000;
|
||||
}
|
||||
.btn-red:focus:not(:active) {
|
||||
box-shadow: 0px 0px 0px 2px rgba(241, 70, 104, 0.25);
|
||||
}
|
||||
|
||||
|
||||
/* Login box styles
|
||||
|
@ -1,9 +1,10 @@
|
||||
function CropEditor(opt) {
|
||||
class CropEditor {
|
||||
constructor(opt) {
|
||||
this.opt = opt;
|
||||
|
||||
this.edit_crop_button = document.createElement("span");
|
||||
this.edit_crop_button.className = "btn";
|
||||
this.edit_crop_button.innerHTML = "Edit crop";
|
||||
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);
|
||||
@ -13,54 +14,83 @@ function CropEditor(opt) {
|
||||
this.toggleCropButton();
|
||||
}
|
||||
|
||||
CropEditor.prototype.buildContainer = function() {
|
||||
initDOM() {
|
||||
this.container = document.createElement("div");
|
||||
this.container.id = "crop_editor";
|
||||
|
||||
this.position = document.createElement("div");
|
||||
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);
|
||||
|
||||
var source_x_label = document.createTextNode("Source X:");
|
||||
let source_x_label = document.createTextNode("Source X:");
|
||||
this.position.appendChild(source_x_label);
|
||||
|
||||
this.source_x = document.createElement("input");
|
||||
this.source_x.type = 'number';
|
||||
this.source_x.addEventListener("change", this.positionBoundary.bind(this));
|
||||
this.source_x.addEventListener("keyup", this.positionBoundary.bind(this));
|
||||
this.position.appendChild(this.source_x);
|
||||
|
||||
var source_y_label = document.createTextNode("Source Y:");
|
||||
let source_y_label = document.createTextNode("Source Y:");
|
||||
this.position.appendChild(source_y_label);
|
||||
|
||||
this.source_y = document.createElement("input");
|
||||
this.source_y.type = 'number';
|
||||
this.source_y.addEventListener("change", this.positionBoundary.bind(this));
|
||||
this.source_y.addEventListener("keyup", this.positionBoundary.bind(this));
|
||||
this.position.appendChild(this.source_y);
|
||||
|
||||
var crop_width_label = document.createTextNode("Crop width:");
|
||||
let crop_width_label = document.createTextNode("Crop width:");
|
||||
this.position.appendChild(crop_width_label);
|
||||
|
||||
this.crop_width = document.createElement("input");
|
||||
this.crop_width.type = 'number';
|
||||
this.crop_width.addEventListener("change", this.positionBoundary.bind(this));
|
||||
this.crop_width.addEventListener("keyup", this.positionBoundary.bind(this));
|
||||
this.position.appendChild(this.crop_width);
|
||||
|
||||
var crop_height_label = document.createTextNode("Crop height:");
|
||||
let crop_height_label = document.createTextNode("Crop height:");
|
||||
this.position.appendChild(crop_height_label);
|
||||
|
||||
this.crop_height = document.createElement("input");
|
||||
this.crop_height.type = 'number';
|
||||
this.crop_height.addEventListener("change", this.positionBoundary.bind(this));
|
||||
this.crop_height.addEventListener("keyup", this.positionBoundary.bind(this));
|
||||
this.position.appendChild(this.crop_height);
|
||||
|
||||
this.crop_constrain_label = document.createElement("label");
|
||||
this.position.appendChild(this.crop_constrain_label);
|
||||
|
||||
this.crop_constrain = document.createElement("input");
|
||||
this.crop_constrain.checked = true;
|
||||
this.crop_constrain.type = 'checkbox';
|
||||
this.crop_constrain_label.appendChild(this.crop_constrain);
|
||||
|
||||
this.crop_constrain_text = document.createTextNode('Constrain proportions');
|
||||
this.crop_constrain_label.appendChild(this.crop_constrain_text);
|
||||
|
||||
this.save_button = document.createElement("span");
|
||||
this.save_button.className = "btn";
|
||||
this.save_button.innerHTML = "Save";
|
||||
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.innerHTML = "Abort";
|
||||
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);
|
||||
@ -70,74 +100,90 @@ CropEditor.prototype.buildContainer = function() {
|
||||
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);
|
||||
}
|
||||
|
||||
this.parent = document.getElementById(this.opt.editor_container_parent_id);
|
||||
this.parent.appendChild(this.container);
|
||||
};
|
||||
|
||||
CropEditor.prototype.setInputValues = function() {
|
||||
var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
||||
|
||||
if (typeof current.crop_region === "undefined") {
|
||||
var source_ratio = this.original_image.naturalWidth / this.original_image.naturalHeight,
|
||||
crop_ratio = current.crop_width / current.crop_height,
|
||||
min_dim = Math.min(this.original_image.naturalWidth, this.original_image.naturalHeight);
|
||||
setDefaultCrop(cropAspectRatio, cropMethod) {
|
||||
let source = this.original_image;
|
||||
let sourceAspectRatio = source.naturalWidth / source.naturalHeight;
|
||||
|
||||
// Cropping from the centre?
|
||||
if (current.crop_method === "c") {
|
||||
if (cropMethod === "c" || cropMethod === "s") {
|
||||
// Crop vertically from the centre, using the entire width.
|
||||
if (source_ratio < crop_ratio) {
|
||||
this.crop_width.value = this.original_image.naturalWidth;
|
||||
this.crop_height.value = Math.ceil(this.original_image.naturalWidth / crop_ratio);
|
||||
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((this.original_image.naturalHeight - this.crop_height.value) / 2);
|
||||
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(current.crop_width * this.original_image.naturalHeight / current.crop_height);
|
||||
this.crop_height.value = this.original_image.naturalHeight;
|
||||
this.source_x.value = Math.ceil((this.original_image.naturalWidth - this.crop_width.value) / 2);
|
||||
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 (source_ratio < crop_ratio) {
|
||||
this.crop_width.value = this.original_image.naturalWidth;
|
||||
this.crop_height.value = Math.floor(this.original_image.naturalHeight / crop_ratio);
|
||||
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 = current.crop_method.indexOf("t") !== -1 ? "0" : this.original_image.naturalHeight - this.crop_height.value;
|
||||
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(this.original_image.naturalHeight * crop_ratio);
|
||||
this.crop_height.value = this.original_image.naturalHeight;
|
||||
this.source_x.value = Math.floor((this.original_image.naturalWidth - this.crop_width.value) / 2);
|
||||
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 {
|
||||
var region = current.crop_region.split(',');
|
||||
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];
|
||||
}
|
||||
};
|
||||
|
||||
CropEditor.prototype.showContainer = function() {
|
||||
this.container.style.display = "block";
|
||||
this.setInputValues();
|
||||
this.positionBoundary();
|
||||
this.crop_width.min = 1;
|
||||
this.crop_height.min = 1;
|
||||
this.source_x.min = 0;
|
||||
this.source_y.min = 0;
|
||||
|
||||
let source = this.original_image;
|
||||
this.crop_width.max = source.naturalWidth;
|
||||
this.crop_height.max = source.naturalHeight;
|
||||
this.source_x.max = source.naturalWidth - 1;
|
||||
this.source_y.max = source.naturalHeight - 1;
|
||||
|
||||
this.crop_constrain_text.textContent = `Constrain proportions (${current.crop_width} × ${current.crop_height})`;
|
||||
}
|
||||
|
||||
CropEditor.prototype.save = function() {
|
||||
var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
||||
var payload = {
|
||||
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,
|
||||
@ -146,16 +192,16 @@ CropEditor.prototype.save = function() {
|
||||
source_x: this.source_x.value,
|
||||
source_y: this.source_y.value
|
||||
};
|
||||
var req = HttpRequest("post", this.opt.submitUrl + "?id=" + this.opt.asset_id + "&updatethumb",
|
||||
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));
|
||||
};
|
||||
}
|
||||
|
||||
CropEditor.prototype.show = function() {
|
||||
show() {
|
||||
if (typeof this.container === "undefined") {
|
||||
this.buildContainer();
|
||||
this.initDOM();
|
||||
}
|
||||
|
||||
// Defer showing and positioning until image is loaded.
|
||||
@ -163,56 +209,166 @@ CropEditor.prototype.show = function() {
|
||||
if (this.original_image.naturalWidth > 0) {
|
||||
this.showContainer();
|
||||
} else {
|
||||
this.original_image.addEventListener("load", function() {
|
||||
this.showContainer();
|
||||
}.bind(this));
|
||||
this.original_image.addEventListener("load", event => this.showContainer());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CropEditor.prototype.hide = function() {
|
||||
hide() {
|
||||
this.container.style.display = "none";
|
||||
};
|
||||
}
|
||||
|
||||
CropEditor.prototype.addEvents = function(event) {
|
||||
var drag_target = document.getElementById(opt.drag_target);
|
||||
drag_target.addEventListener('dragstart', this.dragStart);
|
||||
drag_target.addEventListener('drag', this.drag);
|
||||
drag_target.addEventListener('dragend', this.dragEnd);
|
||||
};
|
||||
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));
|
||||
|
||||
CropEditor.prototype.dragStart = function(event) {
|
||||
console.log(event);
|
||||
event.preventDefault();
|
||||
};
|
||||
this.original_image.addEventListener('mousedown', event => {return false});
|
||||
this.original_image.addEventListener('dragstart', event => {return false});
|
||||
|
||||
CropEditor.prototype.dragEnd = function(event) {
|
||||
console.log(event);
|
||||
};
|
||||
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));
|
||||
|
||||
CropEditor.prototype.drag = function(event) {
|
||||
console.log(event);
|
||||
};
|
||||
window.addEventListener('resize', this.positionBoundary.bind(this));
|
||||
}
|
||||
|
||||
CropEditor.prototype.toggleCropButton = function() {
|
||||
var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
||||
cropSelectionStart(event) {
|
||||
if (this.isMoving) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let dragStartX = event.x - this.image_container.offsetLeft;
|
||||
let dragStartY = event.y - this.image_container.offsetTop;
|
||||
|
||||
if (dragStartX > this.original_image.clientWidth ||
|
||||
dragStartY > this.original_image.clientHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragging = true;
|
||||
this.dragStartX = dragStartX;
|
||||
this.dragStartY = dragStartY;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (this.crop_constrain.checked) {
|
||||
let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
||||
|
||||
let currentAspectRatio = parseInt(this.crop_width.value) / parseInt(this.crop_height.value);
|
||||
let targetAspectRatio = current.crop_width / current.crop_height;
|
||||
|
||||
if (Math.abs(currentAspectRatio - targetAspectRatio) > 0.001) {
|
||||
// Landscape?
|
||||
if (targetAspectRatio > 1.0) {
|
||||
let height = Math.ceil(this.crop_width.value / targetAspectRatio);
|
||||
if (parseInt(this.source_y.value) + height > this.original_image.naturalHeight) {
|
||||
height = this.original_image.naturalHeight - this.source_y.value;
|
||||
}
|
||||
this.crop_width.value = height * targetAspectRatio;
|
||||
this.crop_height.value = height;
|
||||
}
|
||||
// Portrait?
|
||||
else {
|
||||
let width = Math.ceil(this.crop_height.value * targetAspectRatio);
|
||||
if (parseInt(this.source_x.value) + width > this.original_image.naturalWidth) {
|
||||
width = this.original_image.naturalWidth - this.source_x.value;
|
||||
}
|
||||
this.crop_width.value = width;
|
||||
this.crop_height.value = width / targetAspectRatio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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" : "";
|
||||
};
|
||||
}
|
||||
|
||||
CropEditor.prototype.positionBoundary = function(event) {
|
||||
var 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;
|
||||
|
||||
var 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";
|
||||
};
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
@ -138,10 +138,10 @@ class EditAssetForm extends SubTemplate
|
||||
<h3>Thumbnails</h3>
|
||||
View: <select id="thumbnail_src">';
|
||||
|
||||
foreach ($this->thumbs as $thumb)
|
||||
$first = INF;
|
||||
foreach ($this->thumbs as $i => $thumb)
|
||||
{
|
||||
if (!$thumb['status'])
|
||||
continue;
|
||||
$first = min($i, $first);
|
||||
|
||||
echo '
|
||||
<option data-url="', $thumb['url'], '" data-crop_width="', $thumb['dimensions'][0], '" data-crop_height="', $thumb['dimensions'][1], '"',
|
||||
@ -171,18 +171,16 @@ class EditAssetForm extends SubTemplate
|
||||
|
||||
echo '
|
||||
</select>
|
||||
<a id="thumbnail_link" href="', $this->thumbs[0]['url'], '" target="_blank">
|
||||
<img id="thumbnail" src="', $this->thumbs[0]['url'], '" alt="Thumbnail" style="width: 100%; height: auto;">
|
||||
<a id="thumbnail_link" href="', $this->thumbs[$first]['url'], '" target="_blank">
|
||||
<img id="thumbnail" src="', $this->thumbs[$first]['url'], '" alt="Thumbnail" style="width: 100%; height: auto;">
|
||||
</a>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
setTimeout(function() {
|
||||
document.getElementById("thumbnail_src").addEventListener("change", function(event) {
|
||||
var selection = event.target.options[event.target.selectedIndex];
|
||||
<script type="text/javascript" defer="defer">
|
||||
document.getElementById("thumbnail_src").addEventListener("change", event => {
|
||||
let selection = event.target.options[event.target.selectedIndex];
|
||||
document.getElementById("thumbnail_link").href = selection.dataset.url;
|
||||
document.getElementById("thumbnail").src = selection.dataset.url;
|
||||
});
|
||||
}, 100);
|
||||
</script>';
|
||||
}
|
||||
|
||||
@ -193,27 +191,27 @@ class EditAssetForm extends SubTemplate
|
||||
|
||||
echo '
|
||||
<script type="text/javascript" src="', BASEURL, '/js/crop_editor.js"></script>
|
||||
<script type="text/javascript">
|
||||
setTimeout(function() {
|
||||
var editor = new CropEditor({
|
||||
<script type="text/javascript" defer="defer">
|
||||
let editor = new CropEditor({
|
||||
submit_url: "', BASEURL, '/editasset/",
|
||||
original_image_src: "', $this->asset->getUrl(), '",
|
||||
editor_container_parent_id: "asset_form",
|
||||
thumbnail_select_id: "thumbnail_src",
|
||||
drag_target: "drag_target",
|
||||
drag_target: ".crop_image_container",
|
||||
asset_id: ', $this->asset->getId(), ',
|
||||
after_save: function(data) {
|
||||
// Update thumbnail
|
||||
document.getElementById("thumbnail").src = data.url + "?" + (new Date()).getTime();
|
||||
|
||||
// Update select
|
||||
var src = document.getElementById("thumbnail_src");
|
||||
src.options[src.selectedIndex].dataset.crop_region = data.value;
|
||||
let src = document.getElementById("thumbnail_src");
|
||||
let option = src.options[src.selectedIndex];
|
||||
option.dataset.crop_region = data.value;
|
||||
option.textContent = option.textContent.replace(/top|bottom|centre|slice/, "exact");
|
||||
|
||||
// TODO: update meta
|
||||
Roflin
commented
Is this needed for this PR? Is this needed for this PR?
Aaron
commented
Thanks. No, this just still hasn't been implemented; it wasn't before, either. I'd like to refactor the form a little before I take this on. Thanks. No, this just still hasn't been implemented; it wasn't before, either. I'd like to refactor the form a little before I take this on.
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
</script>';
|
||||
}
|
||||
|
||||
@ -256,9 +254,6 @@ class EditAssetForm extends SubTemplate
|
||||
|
||||
foreach ($this->thumbs as $thumb)
|
||||
{
|
||||
if (!$thumb['status'])
|
||||
continue;
|
||||
|
||||
echo '
|
||||
<option value="thumb_', implode('x', $thumb['dimensions']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user
Edge case:
suffix
can be null, as the group is optional in the regex. Handle this.