Merge pull request 'Make crop editor usable' (#22) from crop-editor into master
Reviewed-on: #22
This commit is contained in:
commit
d069ddca18
@ -116,18 +116,25 @@ class EditAsset extends HTMLController
|
|||||||
if (!preg_match('~^(?<width>\d+)x(?<height>\d+)(?<suffix>_c(?<method>[best]?))?$~', $selector, $thumb))
|
if (!preg_match('~^(?<width>\d+)x(?<height>\d+)(?<suffix>_c(?<method>[best]?))?$~', $selector, $thumb))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
$has_crop_boundary = isset($metadata['crop_' . $thumb['width'] . 'x' . $thumb['height']]);
|
$dimensions = $thumb['width'] . 'x' . $thumb['height'];
|
||||||
$has_custom_image = isset($metadata['custom_' . $thumb['width'] . 'x' . $thumb['height']]);
|
|
||||||
|
// Does the thumbnail exist on disk? If not, use an url to generate it.
|
||||||
|
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[] = [
|
$thumbs[] = [
|
||||||
'dimensions' => [(int) $thumb['width'], (int) $thumb['height']],
|
'dimensions' => [(int) $thumb['width'], (int) $thumb['height']],
|
||||||
'cropped' => !$has_custom_image && (!empty($thumb['suffix']) || $has_crop_boundary),
|
'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_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,
|
'custom_image' => $has_custom_image,
|
||||||
'filename' => $filename,
|
'filename' => $filename,
|
||||||
'full_path' => THUMBSDIR . '/' . $subdir . '/' . $filename,
|
'url' => $thumb_url,
|
||||||
'url' => THUMBSURL . '/' . $subdir . '/' . $filename,
|
|
||||||
'status' => file_exists(THUMBSDIR . '/' . $subdir . '/' . $filename),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,18 +151,19 @@ class EditAsset extends HTMLController
|
|||||||
$crop_value = $data->crop_width . ',' . $data->crop_height . ',' . $data->source_x . ',' . $data->source_y;
|
$crop_value = $data->crop_width . ',' . $data->crop_height . ',' . $data->source_x . ',' . $data->source_y;
|
||||||
$meta[$crop_key] = $crop_value;
|
$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;
|
$custom_key = 'custom_' . $data->thumb_width . 'x' . $data->thumb_height;
|
||||||
if (isset($meta[$custom_key]))
|
if (isset($meta[$custom_key]))
|
||||||
|
{
|
||||||
|
// TODO: delete from disk
|
||||||
unset($meta[$custom_key]);
|
unset($meta[$custom_key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save meta changes so far.
|
||||||
|
$image->setMetaData($meta);
|
||||||
|
|
||||||
// Force a rebuild of related thumbnails.
|
// Force a rebuild of related thumbnails.
|
||||||
$thumb_key = 'thumb_' . $data->thumb_width . 'x' . $data->thumb_height;
|
$image->removeThumbnailsOfSize($data->thumb_width, $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);
|
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'key' => $crop_key,
|
'key' => $crop_key,
|
||||||
|
@ -133,9 +133,9 @@ class Image extends Asset
|
|||||||
|
|
||||||
public function removeAllThumbnails()
|
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))
|
if (is_file($thumb_path))
|
||||||
unlink($thumb_path);
|
unlink($thumb_path);
|
||||||
}
|
}
|
||||||
@ -146,6 +146,30 @@ class Image extends Asset
|
|||||||
['id_asset' => $this->id_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)
|
public function replaceThumbnail($descriptor, $tmp_file)
|
||||||
{
|
{
|
||||||
if (!is_file($tmp_file))
|
if (!is_file($tmp_file))
|
||||||
|
@ -164,6 +164,8 @@ class Thumbnail
|
|||||||
$this->filename_suffix .= 's';
|
$this->filename_suffix .= 's';
|
||||||
elseif ($this->crop_mode === self::CROP_MODE_SLICE_BOTTOM)
|
elseif ($this->crop_mode === self::CROP_MODE_SLICE_BOTTOM)
|
||||||
$this->filename_suffix .= 'b';
|
$this->filename_suffix .= 'b';
|
||||||
|
elseif ($this->crop_mode === self::CROP_MODE_BOUNDARY)
|
||||||
|
$this->filename_suffix .= 'e';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
$this->filename_suffix = '';
|
$this->filename_suffix = '';
|
||||||
|
@ -145,46 +145,55 @@ body {
|
|||||||
/* Crop editor
|
/* Crop editor
|
||||||
----------------*/
|
----------------*/
|
||||||
#crop_editor {
|
#crop_editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #000;
|
background: rgba(0, 0, 0, 0.8);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
#crop_editor input {
|
#crop_editor input[type=number] {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
background: #555;
|
background: #555;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.crop_image_container {
|
#crop_editor input[type=checkbox] {
|
||||||
position: relative;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.crop_position {
|
.crop_position {
|
||||||
|
background: rgba(0, 0, 0, 1.0);
|
||||||
|
border: none;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.crop_position input, .crop_position .btn {
|
.crop_position input, .crop_position .btn {
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crop_image_container {
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-height: calc(100% - 34px);
|
||||||
|
}
|
||||||
.crop_image_container img {
|
.crop_image_container img {
|
||||||
height: auto;
|
border: 1px solid #000;
|
||||||
width: auto;
|
max-height: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 700px;
|
|
||||||
}
|
}
|
||||||
#crop_boundary {
|
#crop_boundary {
|
||||||
border: 1px solid rgba(255, 255, 255, 0.75);
|
border: 1px dashed rgb(255, 255, 255);
|
||||||
background: rgba(255, 255, 255, 0.75);
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
cursor: move;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
top: 400px;
|
top: 400px;
|
||||||
left: 300px;
|
left: 300px;
|
||||||
filter: invert(100%); /* temp */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -375,34 +375,59 @@ footer a {
|
|||||||
|
|
||||||
input, select, .btn {
|
input, select, .btn {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #dbdbdb;
|
||||||
|
border-radius: 4px;
|
||||||
color: #000;
|
color: #000;
|
||||||
font: 13px/1.7 "Open Sans", "Helvetica", sans-serif;
|
font: 13px/1.7 "Open Sans", "Helvetica", sans-serif;
|
||||||
padding: 3px;
|
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 {
|
textarea {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #dbdbdb;
|
||||||
font: 12px/1.4 'Monaco', 'Inconsolata', 'DejaVu Sans Mono', monospace;
|
border-radius: 4px;
|
||||||
|
font: 14px/1.4 'Inconsolata', 'DejaVu Sans Mono', monospace;
|
||||||
padding: 0.75%;
|
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 {
|
.btn-red {
|
||||||
background: #F3B076;
|
background: #eebbaa;
|
||||||
border-color: #C98245;
|
border-color: #cc9988;
|
||||||
|
}
|
||||||
|
.btn-red:hover, .btn-red:focus {
|
||||||
|
border-color: #bb7766;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
.btn-red:focus:not(:active) {
|
||||||
|
box-shadow: 0px 0px 0px 2px rgba(241, 70, 104, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Login box styles
|
/* Login box styles
|
||||||
|
@ -1,218 +1,374 @@
|
|||||||
function CropEditor(opt) {
|
class CropEditor {
|
||||||
this.opt = opt;
|
constructor(opt) {
|
||||||
|
this.opt = opt;
|
||||||
|
|
||||||
this.edit_crop_button = document.createElement("span");
|
this.edit_crop_button = document.createElement("span");
|
||||||
this.edit_crop_button.className = "btn";
|
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.edit_crop_button.addEventListener('click', this.show.bind(this));
|
||||||
|
|
||||||
this.thumbnail_select = document.getElementById(opt.thumbnail_select_id);
|
this.thumbnail_select = document.getElementById(opt.thumbnail_select_id);
|
||||||
this.thumbnail_select.addEventListener('change', this.toggleCropButton.bind(this));
|
this.thumbnail_select.addEventListener('change', this.toggleCropButton.bind(this));
|
||||||
this.thumbnail_select.parentNode.insertBefore(this.edit_crop_button, this.thumbnail_select.nextSibling);
|
this.thumbnail_select.parentNode.insertBefore(this.edit_crop_button, this.thumbnail_select.nextSibling);
|
||||||
|
|
||||||
this.toggleCropButton();
|
this.toggleCropButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
CropEditor.prototype.buildContainer = function() {
|
initDOM() {
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.id = "crop_editor";
|
this.container.id = "crop_editor";
|
||||||
|
|
||||||
this.position = document.createElement("div");
|
this.initPositionForm();
|
||||||
this.position.className = "crop_position";
|
this.initImageContainer();
|
||||||
this.container.appendChild(this.position);
|
|
||||||
|
|
||||||
var source_x_label = document.createTextNode("Source X:");
|
this.parent = document.getElementById(this.opt.editor_container_parent_id);
|
||||||
this.position.appendChild(source_x_label);
|
this.parent.appendChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
this.source_x = document.createElement("input");
|
initPositionForm() {
|
||||||
this.source_x.addEventListener("keyup", this.positionBoundary.bind(this));
|
this.position = document.createElement("fieldset");
|
||||||
this.position.appendChild(this.source_x);
|
this.position.className = "crop_position";
|
||||||
|
this.container.appendChild(this.position);
|
||||||
|
|
||||||
var source_y_label = document.createTextNode("Source Y:");
|
let source_x_label = document.createTextNode("Source X:");
|
||||||
this.position.appendChild(source_y_label);
|
this.position.appendChild(source_x_label);
|
||||||
|
|
||||||
this.source_y = document.createElement("input");
|
this.source_x = document.createElement("input");
|
||||||
this.source_y.addEventListener("keyup", this.positionBoundary.bind(this));
|
this.source_x.type = 'number';
|
||||||
this.position.appendChild(this.source_y);
|
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 crop_width_label = document.createTextNode("Crop width:");
|
let source_y_label = document.createTextNode("Source Y:");
|
||||||
this.position.appendChild(crop_width_label);
|
this.position.appendChild(source_y_label);
|
||||||
|
|
||||||
this.crop_width = document.createElement("input");
|
this.source_y = document.createElement("input");
|
||||||
this.crop_width.addEventListener("keyup", this.positionBoundary.bind(this));
|
this.source_y.type = 'number';
|
||||||
this.position.appendChild(this.crop_width);
|
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_height_label = document.createTextNode("Crop height:");
|
let crop_width_label = document.createTextNode("Crop width:");
|
||||||
this.position.appendChild(crop_height_label);
|
this.position.appendChild(crop_width_label);
|
||||||
|
|
||||||
this.crop_height = document.createElement("input");
|
this.crop_width = document.createElement("input");
|
||||||
this.crop_height.addEventListener("keyup", this.positionBoundary.bind(this));
|
this.crop_width.type = 'number';
|
||||||
this.position.appendChild(this.crop_height);
|
this.crop_width.addEventListener("change", this.positionBoundary.bind(this));
|
||||||
|
this.crop_width.addEventListener("keyup", this.positionBoundary.bind(this));
|
||||||
|
this.position.appendChild(this.crop_width);
|
||||||
|
|
||||||
this.save_button = document.createElement("span");
|
let crop_height_label = document.createTextNode("Crop height:");
|
||||||
this.save_button.className = "btn";
|
this.position.appendChild(crop_height_label);
|
||||||
this.save_button.innerHTML = "Save";
|
|
||||||
this.save_button.addEventListener('click', this.save.bind(this));
|
|
||||||
this.position.appendChild(this.save_button);
|
|
||||||
|
|
||||||
this.abort_button = document.createElement("span");
|
this.crop_height = document.createElement("input");
|
||||||
this.abort_button.className = "btn btn-red";
|
this.crop_height.type = 'number';
|
||||||
this.abort_button.innerHTML = "Abort";
|
this.crop_height.addEventListener("change", this.positionBoundary.bind(this));
|
||||||
this.abort_button.addEventListener('click', this.hide.bind(this));
|
this.crop_height.addEventListener("keyup", this.positionBoundary.bind(this));
|
||||||
this.position.appendChild(this.abort_button);
|
this.position.appendChild(this.crop_height);
|
||||||
|
|
||||||
this.image_container = document.createElement("div");
|
this.crop_constrain_label = document.createElement("label");
|
||||||
this.image_container.className = "crop_image_container";
|
this.position.appendChild(this.crop_constrain_label);
|
||||||
this.container.appendChild(this.image_container);
|
|
||||||
|
|
||||||
this.crop_boundary = document.createElement("div");
|
this.crop_constrain = document.createElement("input");
|
||||||
this.crop_boundary.id = "crop_boundary";
|
this.crop_constrain.checked = true;
|
||||||
this.image_container.appendChild(this.crop_boundary);
|
this.crop_constrain.type = 'checkbox';
|
||||||
|
this.crop_constrain_label.appendChild(this.crop_constrain);
|
||||||
|
|
||||||
this.original_image = document.createElement("img");
|
this.crop_constrain_text = document.createTextNode('Constrain proportions');
|
||||||
this.original_image.id = "original_image";
|
this.crop_constrain_label.appendChild(this.crop_constrain_text);
|
||||||
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.save_button = document.createElement("span");
|
||||||
this.parent.appendChild(this.container);
|
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);
|
||||||
|
|
||||||
CropEditor.prototype.setInputValues = function() {
|
this.abort_button = document.createElement("span");
|
||||||
var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof current.crop_region === "undefined") {
|
initImageContainer() {
|
||||||
var source_ratio = this.original_image.naturalWidth / this.original_image.naturalHeight,
|
this.image_container = document.createElement("div");
|
||||||
crop_ratio = current.crop_width / current.crop_height,
|
this.image_container.className = "crop_image_container";
|
||||||
min_dim = Math.min(this.original_image.naturalWidth, this.original_image.naturalHeight);
|
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?
|
// Cropping from the centre?
|
||||||
if (current.crop_method === "c") {
|
if (cropMethod === "c" || cropMethod === "s") {
|
||||||
// Crop vertically from the centre, using the entire width.
|
// Crop vertically from the centre, using the entire width.
|
||||||
if (source_ratio < crop_ratio) {
|
if (sourceAspectRatio <= cropAspectRatio) {
|
||||||
this.crop_width.value = this.original_image.naturalWidth;
|
this.crop_width.value = source.naturalWidth;
|
||||||
this.crop_height.value = Math.ceil(this.original_image.naturalWidth / crop_ratio);
|
this.crop_height.value = Math.ceil(source.naturalWidth / cropAspectRatio);
|
||||||
this.source_x.value = 0;
|
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.
|
// Crop horizontally from the centre, using the entire height.
|
||||||
else {
|
else {
|
||||||
this.crop_width.value = Math.ceil(current.crop_width * this.original_image.naturalHeight / current.crop_height);
|
this.crop_width.value = Math.ceil(cropAspectRatio * source.naturalHeight);
|
||||||
this.crop_height.value = this.original_image.naturalHeight;
|
this.crop_height.value = source.naturalHeight;
|
||||||
this.source_x.value = Math.ceil((this.original_image.naturalWidth - this.crop_width.value) / 2);
|
this.source_x.value = Math.ceil((source.naturalWidth - this.crop_width.value) / 2);
|
||||||
this.source_y.value = 0;
|
this.source_y.value = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cropping a top or bottom slice?
|
// Cropping a top or bottom slice?
|
||||||
else {
|
else {
|
||||||
// Can we actually take a top or bottom slice from the original image?
|
// Can we actually take a top or bottom slice from the original image?
|
||||||
if (source_ratio < crop_ratio) {
|
if (sourceAspectRatio <= cropAspectRatio) {
|
||||||
this.crop_width.value = this.original_image.naturalWidth;
|
this.crop_width.value = source.naturalWidth;
|
||||||
this.crop_height.value = Math.floor(this.original_image.naturalHeight / crop_ratio);
|
this.crop_height.value = Math.floor(source.naturalWidth / cropAspectRatio);
|
||||||
this.source_x.value = "0";
|
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.
|
// Otherwise, take a vertical slice from the centre.
|
||||||
else {
|
else {
|
||||||
this.crop_width.value = Math.floor(this.original_image.naturalHeight * crop_ratio);
|
this.crop_width.value = Math.floor(source.naturalHeight * cropAspectRatio);
|
||||||
this.crop_height.value = this.original_image.naturalHeight;
|
this.crop_height.value = source.naturalHeight;
|
||||||
this.source_x.value = Math.floor((this.original_image.naturalWidth - this.crop_width.value) / 2);
|
this.source_x.value = Math.floor((source.naturalWidth - this.crop_width.value) / 2);
|
||||||
this.source_y.value = "0";
|
this.source_y.value = "0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
var 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
CropEditor.prototype.save = function() {
|
|
||||||
var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
|
||||||
var 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
|
|
||||||
};
|
|
||||||
var 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() {
|
|
||||||
if (typeof this.container === "undefined") {
|
|
||||||
this.buildContainer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer showing and positioning until image is loaded.
|
setPositionFormValues() {
|
||||||
// !!! TODO: add a spinner in the mean time?
|
let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
||||||
if (this.original_image.naturalWidth > 0) {
|
|
||||||
this.showContainer();
|
if (typeof current.crop_region === "undefined") {
|
||||||
} else {
|
let aspectRatio = current.crop_width / current.crop_height;
|
||||||
this.original_image.addEventListener("load", function() {
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
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})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
this.showContainer();
|
||||||
}.bind(this));
|
} else {
|
||||||
|
this.original_image.addEventListener("load", event => this.showContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
CropEditor.prototype.hide = function() {
|
hide() {
|
||||||
this.container.style.display = "none";
|
this.container.style.display = "none";
|
||||||
};
|
}
|
||||||
|
|
||||||
CropEditor.prototype.addEvents = function(event) {
|
addEvents(event) {
|
||||||
var drag_target = document.getElementById(opt.drag_target);
|
let cropTarget = this.image_container;
|
||||||
drag_target.addEventListener('dragstart', this.dragStart);
|
cropTarget.addEventListener('mousedown', this.cropSelectionStart.bind(this));
|
||||||
drag_target.addEventListener('drag', this.drag);
|
cropTarget.addEventListener('mousemove', this.cropSelection.bind(this));
|
||||||
drag_target.addEventListener('dragend', this.dragEnd);
|
cropTarget.addEventListener('mouseup', this.cropSelectionEnd.bind(this));
|
||||||
};
|
// cropTarget.addEventListener('mouseout', this.cropSelectionEnd.bind(this));
|
||||||
|
|
||||||
CropEditor.prototype.dragStart = function(event) {
|
this.original_image.addEventListener('mousedown', event => {return false});
|
||||||
console.log(event);
|
this.original_image.addEventListener('dragstart', event => {return false});
|
||||||
event.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
CropEditor.prototype.dragEnd = function(event) {
|
let moveTarget = this.crop_boundary;
|
||||||
console.log(event);
|
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) {
|
window.addEventListener('resize', this.positionBoundary.bind(this));
|
||||||
console.log(event);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
CropEditor.prototype.toggleCropButton = function() {
|
cropSelectionStart(event) {
|
||||||
var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset;
|
if (this.isMoving) {
|
||||||
this.edit_crop_button.style.display = typeof current.crop_method === "undefined" ? "none" : "";
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
CropEditor.prototype.positionBoundary = function(event) {
|
let dragStartX = event.x - this.image_container.offsetLeft;
|
||||||
var source_x = parseInt(this.source_x.value),
|
let dragStartY = event.y - this.image_container.offsetTop;
|
||||||
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,
|
if (dragStartX > this.original_image.clientWidth ||
|
||||||
height_scale = scaled_height / real_height;
|
dragStartY > this.original_image.clientHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
crop_boundary.style.left = (this.source_x.value) * width_scale + "px";
|
this.isDragging = true;
|
||||||
crop_boundary.style.top = (this.source_y.value) * height_scale + "px";
|
this.dragStartX = dragStartX;
|
||||||
crop_boundary.style.width = (this.crop_width.value) * width_scale + "px";
|
this.dragStartY = dragStartY;
|
||||||
crop_boundary.style.height = (this.crop_height.value) * height_scale + "px";
|
}
|
||||||
};
|
|
||||||
|
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" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
<h3>Thumbnails</h3>
|
||||||
View: <select id="thumbnail_src">';
|
View: <select id="thumbnail_src">';
|
||||||
|
|
||||||
foreach ($this->thumbs as $thumb)
|
$first = INF;
|
||||||
|
foreach ($this->thumbs as $i => $thumb)
|
||||||
{
|
{
|
||||||
if (!$thumb['status'])
|
$first = min($i, $first);
|
||||||
continue;
|
|
||||||
|
|
||||||
echo '
|
echo '
|
||||||
<option data-url="', $thumb['url'], '" data-crop_width="', $thumb['dimensions'][0], '" data-crop_height="', $thumb['dimensions'][1], '"',
|
<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 '
|
echo '
|
||||||
</select>
|
</select>
|
||||||
<a id="thumbnail_link" href="', $this->thumbs[0]['url'], '" target="_blank">
|
<a id="thumbnail_link" href="', $this->thumbs[$first]['url'], '" target="_blank">
|
||||||
<img id="thumbnail" src="', $this->thumbs[0]['url'], '" alt="Thumbnail" style="width: 100%; height: auto;">
|
<img id="thumbnail" src="', $this->thumbs[$first]['url'], '" alt="Thumbnail" style="width: 100%; height: auto;">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript" defer="defer">
|
||||||
setTimeout(function() {
|
document.getElementById("thumbnail_src").addEventListener("change", event => {
|
||||||
document.getElementById("thumbnail_src").addEventListener("change", function(event) {
|
let selection = event.target.options[event.target.selectedIndex];
|
||||||
var selection = event.target.options[event.target.selectedIndex];
|
document.getElementById("thumbnail_link").href = selection.dataset.url;
|
||||||
document.getElementById("thumbnail_link").href = selection.dataset.url;
|
document.getElementById("thumbnail").src = selection.dataset.url;
|
||||||
document.getElementById("thumbnail").src = selection.dataset.url;
|
});
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
</script>';
|
</script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,27 +191,27 @@ class EditAssetForm extends SubTemplate
|
|||||||
|
|
||||||
echo '
|
echo '
|
||||||
<script type="text/javascript" src="', BASEURL, '/js/crop_editor.js"></script>
|
<script type="text/javascript" src="', BASEURL, '/js/crop_editor.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript" defer="defer">
|
||||||
setTimeout(function() {
|
let editor = new CropEditor({
|
||||||
var editor = new CropEditor({
|
submit_url: "', BASEURL, '/editasset/",
|
||||||
submit_url: "', BASEURL, '/editasset/",
|
original_image_src: "', $this->asset->getUrl(), '",
|
||||||
original_image_src: "', $this->asset->getUrl(), '",
|
editor_container_parent_id: "asset_form",
|
||||||
editor_container_parent_id: "asset_form",
|
thumbnail_select_id: "thumbnail_src",
|
||||||
thumbnail_select_id: "thumbnail_src",
|
drag_target: ".crop_image_container",
|
||||||
drag_target: "drag_target",
|
asset_id: ', $this->asset->getId(), ',
|
||||||
asset_id: ', $this->asset->getId(), ',
|
after_save: function(data) {
|
||||||
after_save: function(data) {
|
// Update thumbnail
|
||||||
// Update thumbnail
|
document.getElementById("thumbnail").src = data.url + "?" + (new Date()).getTime();
|
||||||
document.getElementById("thumbnail").src = data.url + "?" + (new Date()).getTime();
|
|
||||||
|
|
||||||
// Update select
|
// Update select
|
||||||
var src = document.getElementById("thumbnail_src");
|
let src = document.getElementById("thumbnail_src");
|
||||||
src.options[src.selectedIndex].dataset.crop_region = data.value;
|
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
|
// TODO: update meta
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 100);
|
|
||||||
</script>';
|
</script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,9 +254,6 @@ class EditAssetForm extends SubTemplate
|
|||||||
|
|
||||||
foreach ($this->thumbs as $thumb)
|
foreach ($this->thumbs as $thumb)
|
||||||
{
|
{
|
||||||
if (!$thumb['status'])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
echo '
|
echo '
|
||||||
<option value="thumb_', implode('x', $thumb['dimensions']);
|
<option value="thumb_', implode('x', $thumb['dimensions']);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user