diff --git a/wire/core/ImageSizerEngine.php b/wire/core/ImageSizerEngine.php
index 1bd721c0..9887d33a 100755
--- a/wire/core/ImageSizerEngine.php
+++ b/wire/core/ImageSizerEngine.php
@@ -109,9 +109,11 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* Possible values: northwest, north, northeast, west, center, east, southwest, south, southeast
* or TRUE to crop to center, or FALSE to disable cropping.
+ * Or array where index 0 is % or px from left, and index 1 is % or px from top. Percent is assumed if
+ * values are number strings that end with %. Pixels are assumed of values are just integers.
* Default is: TRUE
*
- * @var bool
+ * @var bool|array
*
*/
protected $cropping = true;
@@ -834,11 +836,13 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
if(isset($value[$v])) $$v = $value[$v];
}
}
-
+
foreach(array('x', 'y', 'w', 'h') as $k) {
- $v = isset($$k) ? $$k : -1;
- if(!is_int($v) || $v < 0) throw new WireException("Missing or wrong param $k for ImageSizer-cropExtra!");
- if(('w' == $k || 'h' == $k) && 0 == $v) throw new WireException("Wrong param $k for ImageSizer-cropExtra!");
+ $v = (int) (isset($$k) ? $$k : -1);
+ if(!$v && $k == 'w' && $h > 0) $v = $this->getProportionalWidth((int) $h);
+ if(!$v && $k == 'h' && $w > 0) $v = $this->getProportionalHeight((int) $w);
+ if($v < 0) throw new WireException("Missing or wrong param $k=$v for ImageSizer-cropExtra! " . print_r($value, true));
+ if(('w' == $k || 'h' == $k) && 0 == $v) throw new WireException("Wrong param $k=$v for ImageSizer-cropExtra! " . print_r($value, true));
}
$this->cropExtra = array($x, $y, $w, $h);
@@ -1384,7 +1388,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
protected function getCropDimensions(&$w1, &$h1, $gdWidth, $targetWidth, $gdHeight, $targetHeight) {
if(is_string($this->cropping)) {
-
+ // calculate from 8 named cropping points
switch($this->cropping) {
case 'nw':
$w1 = 0;
@@ -1418,20 +1422,45 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
}
} else if(is_array($this->cropping)) {
+ // calculate from specific percent or pixels from left and top
+ // $this->cropping is an array with the following:
+ // index 0 represents % or pixels from left
+ // index 1 represents % or pixels from top
// @interrobang + @u-nikos
- if(strpos($this->cropping[0], '%') === false) $pointX = (int) $this->cropping[0];
- else $pointX = $gdWidth * ((int) $this->cropping[0] / 100);
+ if(strpos($this->cropping[0], '%') === false) {
+ $pointX = (int) $this->cropping[0];
+ } else {
+ $pointX = $gdWidth * ((int) $this->cropping[0] / 100);
+ }
- if(strpos($this->cropping[1], '%') === false) $pointY = (int) $this->cropping[1];
- else $pointY = $gdHeight * ((int) $this->cropping[1] / 100);
+ if(strpos($this->cropping[1], '%') === false) {
+ $pointY = (int) $this->cropping[1];
+ } else {
+ $pointY = $gdHeight * ((int) $this->cropping[1] / 100);
+ }
+
+ /*
+ if(isset($this->cropping[2]) && $this->cropping[2] > 1) {
+ // zoom percent (2-100)
+ $zoom = (int) $this->cropping[2];
+ }
+ */
- if($pointX < $targetWidth / 2) $w1 = 0;
- else if($pointX > ($gdWidth - $targetWidth / 2)) $w1 = $gdWidth - $targetWidth;
- else $w1 = $pointX - $targetWidth / 2;
+ if($pointX < $targetWidth / 2) {
+ $w1 = 0;
+ } else if($pointX > ($gdWidth - $targetWidth / 2)) {
+ $w1 = $gdWidth - $targetWidth;
+ } else {
+ $w1 = $pointX - $targetWidth / 2;
+ }
- if($pointY < $targetHeight / 2) $h1 = 0;
- else if($pointY > ($gdHeight - $targetHeight / 2)) $h1 = $gdHeight - $targetHeight;
- else $h1 = $pointY - $targetHeight / 2;
+ if($pointY < $targetHeight / 2) {
+ $h1 = 0;
+ } else if($pointY > ($gdHeight - $targetHeight / 2)) {
+ $h1 = $gdHeight - $targetHeight;
+ } else {
+ $h1 = $pointY - $targetHeight / 2;
+ }
}
}
diff --git a/wire/core/ImageSizerEngineGD.php b/wire/core/ImageSizerEngineGD.php
index 0ca4b4db..4325a918 100755
--- a/wire/core/ImageSizerEngineGD.php
+++ b/wire/core/ImageSizerEngineGD.php
@@ -102,11 +102,12 @@ class ImageSizerEngineGD extends ImageSizerEngine {
protected function processResize($srcFilename, $dstFilename, $fullWidth, $fullHeight, $finalWidth, $finalHeight) {
$this->modified = false;
+ $isModified = false;
if(isset($this->info['bits'])) $this->imageDepth = $this->info['bits'];
$this->imageFormat = strtoupper(str_replace('image/', '', $this->info['mime']));
if(!in_array($this->imageFormat, $this->validSourceImageFormats())) {
- throw new WireException(sprintf($this->_("loaded file '%s' is not in the list of valid images", basename($dstFilename))));
+ throw new WireException(sprintf($this->_("loaded file '%s' is not in the list of valid images"), basename($dstFilename)));
}
$image = null;
@@ -141,6 +142,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
if($this->rotate || $needRotation) { // @horst
$degrees = $this->rotate ? $this->rotate : $orientations[0];
$image = $this->imRotate($image, $degrees);
+ $isModified = true;
if(abs($degrees) == 90 || abs($degrees) == 270) {
// we have to swap width & height now!
$tmp = array($this->getWidth(), $this->getHeight());
@@ -155,7 +157,10 @@ class ImageSizerEngineGD extends ImageSizerEngine {
} else if($orientations[1] > 0) {
$vertical = $orientations[1] == 2;
}
- if(!is_null($vertical)) $image = $this->imFlip($image, $vertical);
+ if(!is_null($vertical)) {
+ $image = $this->imFlip($image, $vertical);
+ $isModified = true;
+ }
}
// if there is requested to crop _before_ resize, we do it here @horst
@@ -182,6 +187,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$this->prepareImageLayer($image, $imageTemp);
imagecopy($image, $imageTemp, 0, 0, $x, $y, $w, $h);
unset($x, $y, $w, $h);
+ $isModified = true;
// now release the intermediate image and update settings
imagedestroy($imageTemp);
@@ -205,15 +211,12 @@ class ImageSizerEngineGD extends ImageSizerEngine {
// current version is already the desired result, we only may have to compress JPEGs but leave GIF and PNG as is:
- /*
- * the following commented block of code prevents PNG/GIF cropping from working
- if($this->imageType == \IMAGETYPE_PNG || $this->imageType == \IMAGETYPE_GIF) {
+ if(!$isModified && ($this->imageType == \IMAGETYPE_PNG || $this->imageType == \IMAGETYPE_GIF)) {
$result = @copy($srcFilename, $dstFilename);
if(isset($image) && is_resource($image)) @imagedestroy($image); // clean up
if(isset($image)) $image = null;
return $result; // early return !
}
- */
// process JPEGs
if(self::checkMemoryForImage(array(imagesx($image), imagesy($image), 3)) === false) {
@@ -243,10 +246,40 @@ class ImageSizerEngineGD extends ImageSizerEngine {
if(self::checkMemoryForImage(array($bgWidth, $bgHeight, 3)) === false) {
throw new WireException(basename($srcFilename) . " - not enough memory to resize to the intermediate image");
}
+
+ $sourceX = 0;
+ $sourceY = 0;
+ $sourceWidth = $this->image['width'];
+ $sourceHeight = $this->image['height'];
+
+ /*
+ * @todo figure out how to make zoom setting adjust coordinates to imagecopyresampled() calls
+ $zoom = is_array($this->cropping) && isset($this->cropping[2]) ? $this->cropping[2] : 0;
+ if($zoom > 1) {
+ $zoom = $zoom * 0.01;
+ $sourceWidth -= $sourceWidth * $zoom;
+ $sourceHeight -= $sourceHeight * $zoom;
+ $sourceX = $this->image['width'] - ($sourceWidth / 2);
+ $sourceY = $this->image['height'] - ($sourceHeight / 2);
+ $bgX = 0;
+ $bgY = 0;
+ }
+ */
$thumb2 = imagecreatetruecolor($bgWidth, $bgHeight);
$this->prepareImageLayer($thumb2, $image);
- imagecopyresampled($thumb2, $image, 0, 0, 0, 0, $bgWidth, $bgHeight, $this->image['width'], $this->image['height']);
+ imagecopyresampled(
+ $thumb2, // destination image
+ $image, // source image
+ 0, // destination X
+ 0, // destination Y
+ $sourceX, // source X
+ $sourceY, // source Y
+ $bgWidth, // destination width
+ $bgHeight, // destination height
+ $sourceWidth, // source width
+ $sourceHeight // source height
+ );
if(self::checkMemoryForImage(array($finalWidth, $finalHeight, 3)) === false) {
throw new WireException(basename($srcFilename) . " - not enough memory to crop to the final image");
@@ -254,7 +287,18 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$thumb = imagecreatetruecolor($finalWidth, $finalHeight);
$this->prepareImageLayer($thumb, $image);
- imagecopyresampled($thumb, $thumb2, 0, 0, $bgX, $bgY, $finalWidth, $finalHeight, $finalWidth, $finalHeight);
+ imagecopyresampled(
+ $thumb, // destination image
+ $thumb2, // source image
+ 0, // destination X
+ 0, // destination Y
+ $bgX, // source X
+ $bgY, // source Y
+ $finalWidth, // destination width
+ $finalHeight, // destination height
+ $finalWidth, // source width
+ $finalHeight // source height
+ );
imagedestroy($thumb2);
}
@@ -418,7 +462,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$amount = intval($amount / 100 * $this->usmValue);
// apply unsharp mask filter
- return $this->UnsharpMask($im, $amount, $radius, $threshold);
+ return $this->unsharpMask($im, $amount, $radius, $threshold);
}
// if we do not use USM, we use our default sharpening method,
@@ -562,12 +606,12 @@ class ImageSizerEngineGD extends ImageSizerEngine {
for($x = 0; $x < $w - 1; $x++) { // each row
for($y = 0; $y < $h; $y++) { // each pixel
- $rgbOrig = ImageColorAt($img, $x, $y);
+ $rgbOrig = imagecolorat($img, $x, $y);
$rOrig = (($rgbOrig >> 16) & 0xFF);
$gOrig = (($rgbOrig >> 8) & 0xFF);
$bOrig = ($rgbOrig & 0xFF);
- $rgbBlur = ImageColorAt($imgBlur, $x, $y);
+ $rgbBlur = imagecolorat($imgBlur, $x, $y);
$rBlur = (($rgbBlur >> 16) & 0xFF);
$gBlur = (($rgbBlur >> 8) & 0xFF);
@@ -586,20 +630,20 @@ class ImageSizerEngineGD extends ImageSizerEngine {
: $bOrig;
if(($rOrig != $rNew) || ($gOrig != $gNew) || ($bOrig != $bNew)) {
- $pixCol = ImageColorAllocate($img, $rNew, $gNew, $bNew);
- ImageSetPixel($img, $x, $y, $pixCol);
+ $pixCol = imagecolorallocate($img, $rNew, $gNew, $bNew);
+ imagesetpixel($img, $x, $y, $pixCol);
}
}
}
} else {
for($x = 0; $x < $w; $x++) { // each row
for($y = 0; $y < $h; $y++) { // each pixel
- $rgbOrig = ImageColorAt($img, $x, $y);
+ $rgbOrig = imagecolorat($img, $x, $y);
$rOrig = (($rgbOrig >> 16) & 0xFF);
$gOrig = (($rgbOrig >> 8) & 0xFF);
$bOrig = ($rgbOrig & 0xFF);
- $rgbBlur = ImageColorAt($imgBlur, $x, $y);
+ $rgbBlur = imagecolorat($imgBlur, $x, $y);
$rBlur = (($rgbBlur >> 16) & 0xFF);
$gBlur = (($rgbBlur >> 8) & 0xFF);
@@ -624,7 +668,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$bNew = 0;
}
$rgbNew = ($rNew << 16) + ($gNew << 8) + $bNew;
- ImageSetPixel($img, $x, $y, $rgbNew);
+ imagesetpixel($img, $x, $y, $rgbNew);
}
}
}
diff --git a/wire/core/Pageimage.php b/wire/core/Pageimage.php
index 879c694b..06dc1878 100644
--- a/wire/core/Pageimage.php
+++ b/wire/core/Pageimage.php
@@ -24,18 +24,20 @@
* ~~~~~
* #pw-body
*
- * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
+ * ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
- * @property int $width Width of image, in pixels.
- * @property int $height Height of image, in pixels.
- * @property int $hidpiWidth HiDPI width of image, in pixels. #pw-internal
- * @property int $hidpiHeight HiDPI heigh of image, in pixels. #pw-internal
- * @property string $error Last image resizing error message, when applicable. #pw-group-resize-and-crop
- * @property Pageimage $original Reference to original $image, if this is a resized version. #pw-group-variations
- * @property string $url
- * @property string $basename
- * @property string $filename
+ * @property-read int $width Width of image, in pixels.
+ * @property-read int $height Height of image, in pixels.
+ * @property-read int $hidpiWidth HiDPI width of image, in pixels. #pw-internal
+ * @property-read int $hidpiHeight HiDPI heigh of image, in pixels. #pw-internal
+ * @property-read string $error Last image resizing error message, when applicable. #pw-group-resize-and-crop
+ * @property-read Pageimage $original Reference to original $image, if this is a resized version. #pw-group-variations
+ * @property-read string $url
+ * @property-read string $basename
+ * @property-read string $filename
+ * @property-read array $focus Focus array contains 'top' (float), 'left' (float), 'zoom' (int), and 'default' (bool) properties.
+ * @property-read bool $hasFocus Does this image have custom focus settings? (i.e. $focus['default'] == true)
*
* @method bool|array isVariation($basename, $allowSelf = false)
* @method Pageimage crop($x, $y, $width, $height, $options = array())
@@ -177,6 +179,119 @@ class Pageimage extends Pagefile {
}
}
+ /**
+ * Get or set focus area for crops to use
+ *
+ * These settings are used by $this->size() calls that specify BOTH width AND height. Focus helps to
+ * ensure that the important subject of the photo is not cropped out when the requested size proportion
+ * differs from the original image proportion. For example, not chopping off someone’s head in a photo.
+ *
+ * Default behavior is to return an array containing "top" and "left" indexes, representing percentages
+ * from top and left. When arguments are specified, you are either setting the top/left percentages, or
+ * unsetting focus, or getting focus in different ways, described in arguments below.
+ *
+ * A zoom argument/property is also present here for future use, but not currently supported.
+ *
+ * #pw-group-other
+ *
+ * @param null|float|int|array|false $top Omit to get focus array, or specify one of the following:
+ * - GET: Omit all arguments to get focus array (default behavior).
+ * - GET: Specify boolean TRUE to return TRUE if focus data is present or FALSE if not.
+ * - GET: Specify integer 1 to make this method return pixel dimensions rather than percentages.
+ * - SET: Specify both $top and $left arguments to set (values assumed to be percentages).
+ * - SET: Specify array containing "top" and "left" indexes to set (percentages).
+ * - SET: Specify array where index 0 is top and index 1 is left (percentages).
+ * - SET: Specify string in the format "top left", i.e. "25 70" (percentages).
+ * - UNSET: Specify boolean false to remove any focus values.
+ * @param null|float|int $left Set left value (when $top value is float|int)
+ * - This argument is only used when setting focus and should be omitted otherwise.
+ * @param null|int $zoom Zoom percent (not currently supported)
+ * @return array|bool|Pageimage Returns one of the following:
+ * - When getting returns array containing top, left and default properties.
+ * - When TRUE was specified for the $top argument, it returns either TRUE (has focus) or FALSE (does not have).
+ * - When setting or unsetting returns $this.
+ *
+ */
+ public function focus($top = null, $left = null, $zoom = null) {
+
+ if(is_string($top) && strpos($top, ' ') && $left === null) {
+ // SET string like "25 70 0" (representing "top left zoom")
+ if(strpos($top, ' ') != strrpos($top, ' ')) {
+ // with zoom
+ list($top, $left, $zoom) = explode(' ', $top, 3);
+ } else {
+ // without zoom
+ list($top, $left) = explode(' ', $top, 2);
+ $zoom = 0;
+ }
+ }
+
+ if($top === null || $top === true || ($top === 1 && $left === null)) {
+ // GET
+ $focus = $this->filedata('focus');
+ if(!is_array($focus) || empty($focus)) {
+ // use default
+ if($top === true) return false;
+ $focus = array(
+ 'top' => 50,
+ 'left' => 50,
+ 'zoom' => 0,
+ 'default' => true,
+ 'str' => '50 50 0',
+ );
+ } else {
+ // use custom
+ if($top === true) return true;
+ if(!isset($focus['zoom'])) $focus['zoom'] = 0;
+ $focus['default'] = false;
+ $focus['str'] = "$focus[top] $focus[left] $focus[zoom]";
+ }
+ if($top === 1) {
+ // return pixel dimensions rather than percentages
+ $centerX = ($focus['left'] / 100) * $this->width(); // i.e. (50 / 100) * 500 = 250;
+ $centerY = ($focus['top'] / 100) * $this->height();
+ $focus['left'] = $centerX;
+ $focus['top'] = $centerY;
+ }
+ return $focus;
+
+ } else if($top === false) {
+ // UNSET
+ $this->filedata(false, 'focus');
+
+ } else if($top !== null && $left !== null) {
+ // SET
+ if(is_array($top)) {
+ if(isset($top['left'])) {
+ $left = $top['left'];
+ $top = $top['top'];
+ $zoom = isset($top['zoom']) ? $top['zoom'] : 0;
+ } else {
+ $top = $top[0];
+ $left = $top[1];
+ $zoom = isset($top[2]) ? $top[2] : 0;
+ }
+ }
+
+ $top = (float) $top;
+ $left = (float) $left;
+ $zoom = (int) $zoom;
+
+ if(((int) $top) == 50 && ((int) $left) == 50 && ($zoom < 2)) {
+ // if matches defaults, then no reason to store in filedata
+ $this->filedata(false, 'focus');
+ } else {
+ $this->filedata('focus', array(
+ 'top' => round($top, 1),
+ 'left' => round($left, 1),
+ 'zoom' => $zoom
+ ));
+ }
+ }
+
+ return $this;
+ }
+
/**
* Get a property from this Pageimage
*
@@ -206,6 +321,12 @@ class Pageimage extends Pagefile {
case 'error':
$value = $this->error;
break;
+ case 'focus':
+ $value = $this->focus();
+ break;
+ case 'hasFocus':
+ $value = $this->focus(true);
+ break;
default:
$value = parent::get($key);
}
@@ -217,7 +338,8 @@ class Pageimage extends Pagefile {
*
* #pw-internal
*
- * @param bool $reset
+ * @param bool|string $reset Specify true to retrieve info fresh, or filename to check and return info for.
+ * When specifying a filename, the info is only returned (not populated with this object).
* @return array
*
*/
@@ -226,22 +348,30 @@ class Pageimage extends Pagefile {
if($reset) $checkImage = true;
else if($this->imageInfo['width']) $checkImage = false;
else $checkImage = true;
+
+ $imageInfo = $this->imageInfo;
+ $filename = is_string($reset) && file_exists($reset) ? $reset : '';
- if($checkImage) {
+ if($checkImage || $filename) {
if($this->ext == 'svg') {
- $info = $this->getImageInfoSVG();
- $this->imageInfo['width'] = $info['width'];
- $this->imageInfo['height'] = $info['height'];
+ $info = $this->getImageInfoSVG($filename);
+ $imageInfo['width'] = $info['width'];
+ $imageInfo['height'] = $info['height'];
} else {
- $info = @getimagesize($this->filename);
+ if($filename) {
+ $info = @getimagesize($filename);
+ } else {
+ $info = @getimagesize($this->filename);
+ }
if($info) {
- $this->imageInfo['width'] = $info[0];
- $this->imageInfo['height'] = $info[1];
+ $imageInfo['width'] = $info[0];
+ $imageInfo['height'] = $info[1];
}
}
+ if(!$filename) $this->imageInfo = $imageInfo;
}
- return $this->imageInfo;
+ return $imageInfo;
}
/**
@@ -251,13 +381,15 @@ class Pageimage extends Pagefile {
*
* #pw-internal
*
+ * @param string $filename Optional filename to check
* @return array of width and height
*
*/
- protected function getImageInfoSVG() {
+ protected function getImageInfoSVG($filename = '') {
$width = 0;
$height = 0;
- $xml = @file_get_contents($this->filename);
+ if(!$filename) $filename = $this->filename;
+ $xml = @file_get_contents($filename);
if($xml) {
$a = @simplexml_load_string($xml)->attributes();
@@ -268,7 +400,7 @@ class Pageimage extends Pagefile {
if((!$width || !$height) && (extension_loaded('imagick') || class_exists('\IMagick'))) {
try {
$imagick = new \Imagick();
- $imagick->readImage($this->filename);
+ $imagick->readImage($filename);
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
} catch(\Exception $e) {
@@ -329,7 +461,7 @@ class Pageimage extends Pagefile {
*
* - `quality` (int): Quality setting 1-100 (default=90, or as specified in /site/config.php).
* - `upscaling` (bool): Allow image to be upscaled? (default=true).
- * - `cropping` (string|bool): Cropping mode, see possible values in "cropping" section below (default=center).
+ * - `cropping` (string|bool|array): Cropping mode, see possible values in "cropping" section below (default=center).
* - `suffix` (string|array): Suffix word to identify the new image, or use array of words for multiple (default=none).
* - `forceNew` (bool): Force re-creation of the image even if it already exists? (default=false).
* - `sharpening` (string): Sharpening mode: "none", "soft", "medium", or "strong" (default=soft).
@@ -339,10 +471,16 @@ class Pageimage extends Pagefile {
* - `hidpi` (bool): Use HiDPI/retina pixel doubling? (default=false).
* - `hidpiQuality` (bool): Quality setting for HiDPI (default=40, typically lower than regular quality setting).
* - `cleanFilename` (bool): Clean filename of historical resize information for shorter filenames? (default=false).
+ * - `nameWidth` (int): Width to use for filename (default is to use specified $width argument).
+ * - `nameHeight` (int): Height to use for filename (default is to use specified $height argument).
+ * - `focus` (bool): Should resizes that result in crop use focus area if available? (default=true).
+ * In order for focus to be applicable, resize must include both width and height.
*
* **Possible values for "cropping" option**
*
- * - `center` (string): to crop to center of image, default behavior.
+ * - `true` (bool): Auto detect and allow use of focus (default).
+ * - `false` (bool): Disallow cropping.
+ * - `center` (string): to crop to center of image.
* - `x111y222` (string): to crop by pixels, 111px from left and 222px from top (replacing 111 and 222 with your values).
* - `north` (string): Crop North (top), may also be just "n".
* - `northwest` (string): Crop from Northwest (top left), may also be just "nw".
@@ -353,7 +491,9 @@ class Pageimage extends Pagefile {
* - `west` (string): Crop West (left), may also be just "w".
* - `east` (string): Crop East (right), may alos be just "e".
* - `blank` (string): Specify a blank string to disallow cropping during resize.
- *
+ * - `array(111,222)` (array): Array of integers index 0 is left pixels and index 1 is top pixels.
+ * - `array('11%','22%')` (array): Array of '%' appended strings where index 0 is left percent and index 1 is top percent.
+ *
* **Note about "quality" and "upscaling" options**
*
* ProcessWire doesn't keep separate copies of images with different "quality" or "upscaling" values.
@@ -436,6 +576,9 @@ class Pageimage extends Pagefile {
'cleanFilename' => false, // clean filename of historial resize information
'rotate' => 0,
'flip' => '',
+ 'nameWidth' => null, // override width to use for filename, int when populated
+ 'nameHeight' => null, // override height to use for filename, int when populated
+ 'focus' => true, // allow single dimension resizes to use focus area?
);
$this->error = '';
@@ -443,16 +586,25 @@ class Pageimage extends Pagefile {
$configOptions = $this->wire('config')->imageSizerOptions;
if(!is_array($configOptions)) $configOptions = array();
$options = array_merge($defaultOptions, $configOptions, $options);
+ if($options['cropping'] === 1) $options['cropping'] = true;
$width = (int) $width;
$height = (int) $height;
-
- if(is_string($options['cropping'])
+
+ if($options['cropping'] === true && empty($options['cropExtra']) && $options['focus'] && $this->hasFocus) {
+ // crop to focus area
+ $focus = $this->focus();
+ $focus['zoom'] = 0; // not yet supported
+ $options['cropping'] = array("$focus[left]%", "$focus[top]%", "$focus[zoom]");
+ $crop = ''; // do not add suffix
+
+ } else if(is_string($options['cropping'])
&& strpos($options['cropping'], 'x') === 0
&& preg_match('/^x(\d+)[yx](\d+)/', $options['cropping'], $matches)) {
$options['cropping'] = true;
$options['cropExtra'] = array((int) $matches[1], (int) $matches[2], $width, $height);
$crop = '';
+
} else {
$crop = ImageSizer::croppingValueStr($options['cropping']);
}
@@ -462,9 +614,15 @@ class Pageimage extends Pagefile {
$options['suffix'] = empty($options['suffix']) ? array() : explode(' ', $options['suffix']);
}
- if($options['rotate'] && !in_array(abs((int) $options['rotate']), array(90, 180, 270))) $options['rotate'] = 0;
- if($options['rotate']) $options['suffix'][] = ($options['rotate'] > 0 ? "rot" : "tor") . abs($options['rotate']);
- if($options['flip']) $options['suffix'][] = strtolower(substr($options['flip'], 0, 1)) == 'v' ? 'flipv' : 'fliph';
+ if($options['rotate'] && !in_array(abs((int) $options['rotate']), array(90, 180, 270))) {
+ $options['rotate'] = 0;
+ }
+ if($options['rotate']) {
+ $options['suffix'][] = ($options['rotate'] > 0 ? "rot" : "tor") . abs($options['rotate']);
+ }
+ if($options['flip']) {
+ $options['suffix'][] = strtolower(substr($options['flip'], 0, 1)) == 'v' ? 'flipv' : 'fliph';
+ }
$suffixStr = '';
if(!empty($options['suffix'])) {
@@ -483,20 +641,28 @@ class Pageimage extends Pagefile {
if($options['hidpiQuality']) $options['quality'] = $options['hidpiQuality'];
}
- //$basename = $this->pagefiles->cleanBasename($this->basename(), false, false, false);
- // cleanBasename($basename, $originalize = false, $allowDots = true, $translate = false)
$originalName = $this->basename();
- $basename = basename($originalName, "." . $this->ext()); // i.e. myfile
+ // determine basename without extension, i.e. myfile
+ $basename = basename($originalName, "." . $this->ext());
$originalSize = $debug ? @filesize($this->filename) : 0;
+
if($options['cleanFilename'] && strpos($basename, '.') !== false) {
$basename = substr($basename, 0, strpos($basename, '.'));
}
- $basename .= '.' . $width . 'x' . $height . $crop . $suffixStr . "." . $this->ext(); // i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg
+
+ // filename uses requested width/height unless another specified via nameWidth or nameHeight options
+ $nameWidth = is_int($options['nameWidth']) ? $options['nameWidth'] : $width;
+ $nameHeight = is_int($options['nameHeight']) ? $options['nameHeight'] : $height;
+
+ // i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg
+ $basename .= '.' . $nameWidth . 'x' . $nameHeight . $crop . $suffixStr . "." . $this->ext();
$filenameFinal = $this->pagefiles->path() . $basename;
$filenameUnvalidated = '';
$exists = file_exists($filenameFinal);
+ // create a new resize if it doesn't already exist or forceNew option is set
if(!$exists || $options['forceNew']) {
+ // filenameUnvalidated is temporary filename used for resize
$filenameUnvalidated = $this->pagefiles->page->filesManager()->getTempPath() . $basename;
if($exists && $options['forceNew']) @unlink($filenameFinal);
if(file_exists($filenameUnvalidated)) @unlink($filenameUnvalidated);
@@ -913,6 +1079,7 @@ class Pageimage extends Pagefile {
* - `1` (int): Rebuild all non-suffix variations, and those w/suffix specifed in $suffix argument. ($suffix is INCLUSION list)
* - `2` (int): Rebuild all variations, except those with suffix specified in $suffix argument. ($suffix is EXCLUSION list)
* - `3` (int): Rebuild only variations specified in the $suffix argument. ($suffix is ONLY-INCLUSION list)
+ * - `4` (int): Rebuild only non-proportional, non-crop variations (variations that specify both width and height)
*
* Mode 0 is the only truly safe mode, as in any other mode there are possibilities that the resulting
* rebuild of the variation may not be exactly what was intended. The issues with other modes primarily
@@ -940,7 +1107,7 @@ class Pageimage extends Pagefile {
$options['forceNew'] = true;
foreach($this->getVariations(array('info' => true)) as $info) {
-
+
$o = $options;
unset($o['cropping']);
$skip = false;
@@ -984,6 +1151,11 @@ class Pageimage extends Pagefile {
}
}
+ if($mode == 4 && ($info['width'] == 0 || $info['height'] == 0)) {
+ // skip images that don't specify both width and height
+ $skip = true;
+ }
+
if($skip) {
$skipped[] = $name;
continue;
@@ -994,12 +1166,32 @@ class Pageimage extends Pagefile {
$o['suffix'] = $info['suffix'];
if(is_file($info['path'])) unlink($info['path']);
+ if(!$info['width'] && $info['actualWidth']) {
+ $info['width'] = $info['actualWidth'];
+ $options['nameWidth'] = 0;
+ }
+ if(!$info['height'] && $info['actualHeight']) {
+ $info['height'] = $info['actualHeight'];
+ $options['nameHeight'] = 0;
+ }
+
if($info['crop'] && preg_match('/^x(\d+)y(\d+)$/', $info['crop'], $matches)) {
+ // dimensional cropping info contained in filename
$cropX = (int) $matches[1];
$cropY = (int) $matches[2];
- $variation = $this->crop($cropX, $cropY, $info['width'], $info['height'], $options);
+ $variation = $this->crop($cropX, $cropY, $info['width'], $info['height'], $options);
+
+ } else if($info['crop']) {
+ // direct cropping info contained in filename
+ $options['cropping'] = $info['crop'];
+ $variation = $this->size($info['width'], $info['height'], $options);
+
+ } else if($this->hasFocus) {
+ // crop to focus area, which the size() method will determine on its own
+ $variation = $this->size($info['width'], $info['height'], $options);
+
} else {
- if($info['crop']) $options['cropping'] = $info['crop'];
+ // no crop, no focus, just resize
$variation = $this->size($info['width'], $info['height'], $options);
}
@@ -1027,8 +1219,10 @@ class Pageimage extends Pagefile {
* - `original` (string): Original basename
* - `url` (string): URL to image
* - `path` (string): Full path + filename to image
- * - `width` (int): Specified width
- * - `height` (int): Specified height
+ * - `width` (int): Specified width in filename
+ * - `height` (int): Specified height in filename
+ * - `actualWidth` (int): Actual width when checked manually
+ * - `actualHeight` (int): Acual height when checked manually
* - `crop` (string): Cropping info string or blank if none
* - `suffix` (array): Array of suffixes
*
@@ -1152,7 +1346,10 @@ class Pageimage extends Pagefile {
} else {
return false;
}
-
+
+ $actualInfo = $this->getImageInfo($info['path']);
+ $info['actualWidth'] = $actualInfo['width'];
+ $info['actualHeight'] = $actualInfo['height'];
$info['hidpiWidth'] = $this->hidpiWidth(0, $info['width']);
$info['hidpiHeight'] = $this->hidpiWidth(0, $info['height']);
diff --git a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.css b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.css
index 25a7e894..fc1743a8 100755
--- a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.css
+++ b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.css
@@ -61,7 +61,6 @@
position: absolute;
top: 50%;
left: 50%;
- transition: transform ease .3s;
-ms-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0); }
.gridImage.gridImagePlaceholder .gridImage__overflow {
@@ -298,13 +297,22 @@
min-height: 1em;
padding: 20px; }
.InputfieldImageEdit__imagewrapper > div {
- width: 100%; }
+ width: 100%;
+ position: relative; }
.InputfieldImageEdit__imagewrapper .detail {
- display: block;
text-align: center;
- opacity: 0;
margin-top: 2px; }
- .InputfieldImageEdit__imagewrapper:hover .detail {
+ .InputfieldImageEdit__imagewrapper .detail-upload {
+ display: block;
+ opacity: 0; }
+ .InputfieldImageEdit__imagewrapper .detail-focus {
+ display: none; }
+ .InputfieldImageEdit__imagewrapper:hover .detail-upload {
+ opacity: 0.7; }
+ .InputfieldImageEdit__imagewrapper > div.focusWrap .detail-upload {
+ display: none; }
+ .InputfieldImageEdit__imagewrapper > div.focusWrap .detail-focus {
+ display: block;
opacity: 0.7; }
.InputfieldImageEdit__replace img {
opacity: 0.5; }
@@ -351,6 +359,29 @@
.InputfieldImageEdit .InputfieldFileDescription + .InputfieldFileTags {
margin-top: 0.5em; }
+.gridImage__overflow .focusArea,
+.InputfieldImageEdit .focusArea {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ color: #fff;
+ cursor: default; }
+ .gridImage__overflow .focusArea.focusActive,
+ .InputfieldImageEdit .focusArea.focusActive {
+ display: block;
+ overflow: hidden; }
+ .gridImage__overflow .focusArea .focusCircle,
+ .InputfieldImageEdit .focusArea .focusCircle {
+ cursor: move;
+ width: 40px;
+ height: 40px;
+ border: 3px solid #fff;
+ border-radius: 50%;
+ background-color: rgba(255, 255, 255, 0.3); }
+
.InputfieldImage .ImageData {
display: none; }
@@ -431,6 +462,8 @@
.InputfieldImageEditAll .gridImage__overflow > img {
display: block;
position: static !important;
+ top: 0;
+ left: 0;
transition: none;
-ms-transform: none;
transform: none;
@@ -438,6 +471,10 @@
max-width: 100% important;
height: initial !important;
cursor: move; }
+ .InputfieldImageEditAll .gridImage__overflow > .focusArea {
+ position: absolute;
+ top: 10px;
+ left: 10px; }
.InputfieldImageEditAll .gridImage .ImageData {
position: relative;
float: left;
diff --git a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.js b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.js
index 6757d664..9ba156df 100755
--- a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.js
+++ b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.js
@@ -30,6 +30,9 @@ function InputfieldImage($) {
// grid items to retry for sizing by setGridSize() methods
var retryGridItems = [];
+ // whether or not zoom-focus feature is available
+ var useZoomFocus = false;
+
/**
* Whether or not AJAX drag/drop upload is allowed?
*
@@ -125,7 +128,7 @@ function InputfieldImage($) {
});
$el.removeClass('InputfieldImageSorting');
},
- cancel: ".InputfieldImageEdit,input,textarea,button,select,option"
+ cancel: ".InputfieldImageEdit,.focusArea,input,textarea,button,select,option"
};
$el.sortable(sortableOptions);
@@ -280,6 +283,10 @@ function InputfieldImage($) {
*
*/
function windowResize() {
+ $('.focusArea.focusActive').each(function() {
+ var $edit = $(this).closest('.InputfieldImageEdit, .gridImage');
+ if($edit.length) stopFocus($edit);
+ });
updateGrid();
checkInputfieldWidth();
}
@@ -318,12 +325,11 @@ function InputfieldImage($) {
*/
function setupEdit($el, $edit) {
- if($el.closest('.InputfieldImageEditAll').length) return;
+ if($el.closest('.InputfieldImageEditAll').length) return; // edit all mode
var $img = $edit.find(".InputfieldImageEdit__image");
var $thumb = $el.find("img");
-
$img.attr({
src: $thumb.attr("data-original"),
"data-original": $thumb.attr("data-original"),
@@ -348,7 +354,6 @@ function InputfieldImage($) {
.find("img")
.add($img)
.magnificPopup(options);
- //.addClass('magnificInit');
// move all of the .ImageData elements to the edit panel
$edit.find(".InputfieldImageEdit__edit")
@@ -356,6 +361,247 @@ function InputfieldImage($) {
.append($el.find(".ImageData").children().not(".InputfieldFileSort"));
}
+ /**
+ * Setup image for a draggable focus area and optional zoom slider
+ *
+ * @param $edit Image editor container (.InputfieldImageEdit or .gridImage)
+ *
+ */
+ function startFocus($edit) {
+
+ var $img, $el, $thumb, $input, $focusArea, $focusCircle, $inputfield,
+ focusData = null, gridSize, mode,
+ $zoomSlider, $zoomBox, lastZoomPercent = 0,
+ lastZoomVisual = 0, startZoomVisual = -1;
+
+ $inputfield = $edit.closest('.Inputfield');
+ gridSize = getCookieData($inputfield, 'size');
+ mode = getCookieData($inputfield, 'mode');
+
+ if($edit.hasClass('gridImage')) {
+ // list mode
+ $el = $edit;
+ $img = $edit.find('.gridImage__overflow').find('img');
+ $thumb = $img;
+ } else {
+ // thumbnail click for editor mode
+ $el = $('#' + $edit.attr('data-for'));
+ $img = $edit.find('.InputfieldImageEdit__image');
+ $thumb = $el.find('.gridImage__overflow').find('img');
+ }
+
+ // get the focus object, optionally for a specific focusStr
+ function getFocus(focusStr) {
+ if(typeof focusStr == "undefined") {
+ if(focusData !== null) return focusData;
+ var $input = $edit.find('.InputfieldImageFocus');
+ var focusStr = $input.val();
+ }
+ var a = focusStr.split(' ');
+ var data = {
+ 'top': (typeof a[0] == "undefined" ? 50.0 : parseFloat(a[0])),
+ 'left': (typeof a[1] == "undefined" ? 50.0 : parseFloat(a[1])),
+ 'zoom': (typeof a[2] == "undefined" ? 0 : parseInt(a[2]))
+ };
+ focusData = data;
+ return data;
+ }
+
+ // get focus string
+ function getFocusStr(focusObj) {
+ if(typeof focusObj == "undefined") focusObj = getFocus();
+ return focusObj.top + ' ' + focusObj.left + ' ' + focusObj.zoom;
+ }
+
+ // get single focus property: top left or zoom
+ function getFocusProperty(property) {
+ var focus = getFocus();
+ return focus[property];
+ }
+
+ // set focus for top left and zoom
+ function setFocus(focusObj) {
+ focusData = focusObj;
+ var focusStr = focusObj.top + ' ' + focusObj.left + ' ' + focusObj.zoom;
+ $thumb.attr('data-focus', focusStr); // for consumption outside startFocus()
+ $input = $edit.find('.InputfieldImageFocus');
+ if(focusStr != $input.val()) {
+ $input.val(focusStr).trigger('change');
+ }
+ }
+
+ // set just one focus property (top, left or zoom)
+ function setFocusProperty(property, value) {
+ var focus = getFocus();
+ focus[property] = value;
+ setFocus(focus);
+ }
+
+ // Set the position of the draggable focus item
+ function setFocusDragPosition() {
+ var focus = getFocus();
+ var $overlay = $focusCircle.parent();
+ var w = $overlay.width();
+ var h = $overlay.height();
+ var x = Math.round(((focus.left / 100) * w) - ($focusCircle.width() / 1.7));
+ var y = Math.round(((focus.top / 100) * h) - ($focusCircle.height() / 2.3));
+ if(x < 0) x = 0;
+ if(y < 0) y = 0;
+ $focusCircle.css({
+ 'top': y + 'px',
+ 'left': x + 'px'
+ });
+ }
+
+ // setup focus area (div that contains all the focus stuff)
+ $focusArea = $img.siblings('.focusArea');
+ if(!$focusArea.length) {
+ $focusArea = $('
').addClass('focusArea');
+ $img.after($focusArea);
+ }
+ $focusArea.css({
+ 'height': $img.height() + 'px',
+ 'width': $img.width() + 'px'
+ }).addClass('focusActive');
+
+ // set the draggable circle for focus
+ $focusCircle = $focusArea.find('.focusCircle');
+ if(!$focusCircle.length) {
+ $focusCircle = $("").addClass('focusCircle');
+ $focusArea.append($focusCircle);
+ }
+
+ // indicate active state for focusing, used by stopFocus()
+ $img.parent().addClass('focusWrap');
+
+ // set the initial position for the focus circle
+ setFocusDragPosition();
+
+ // function called whenever the slider is moved
+ var zoomSlide = function(zoomPercent) {
+ if(typeof zoomPercent == "undefined") zoomPercent = lastZoomPercent;
+ lastZoomPercent = zoomPercent;
+ var w = (100 - zoomPercent) + '%';
+ $zoomBox.width(w);
+ var zoomBoxSize = $zoomBox.width();
+ var focusCircleSize = $focusCircle.height();
+ $zoomBox.height(zoomBoxSize)
+
+ var zoom = zoomPercent;
+ var top = parseInt($focusCircle.css('top')); // top of drag item
+ top += Math.floor(focusCircleSize / 2); // plus half the height of drag item
+ top -= Math.ceil(zoomBoxSize / 2) - 3; // minus half the height of the zoom box (-3)
+
+ var left = parseInt($focusCircle.css('left'));
+ left += Math.floor(focusCircleSize / 2);
+ left -= Math.ceil(zoomBoxSize / 2) - 3;
+
+ if(top < 0) top = 0;
+ if(left < 0) left = 0;
+ // constrain to corners
+ if(top + zoomBoxSize > $focusArea.height()) top = $focusArea.height() - zoomBoxSize;
+ if(left + zoomBoxSize > $focusArea.width()) left = $focusArea.width() - zoomBoxSize;
+
+ $zoomBox.css({
+ top: top + 'px',
+ left: left + 'px'
+ });
+
+ setFocusProperty('zoom', zoomPercent);
+
+ // determine when to visually start showing zoom (in grid mode)
+ var zoomVisual = zoomPercent;
+ if(zoomBoxSize > $img.height() || zoomBoxSize > $img.width()) {
+ zoomVisual = 0;
+ } else {
+ if(!lastZoomVisual) startZoomVisual = zoomVisual;
+ zoomVisual = (zoomVisual - startZoomVisual)+1;
+ }
+ if(mode == 'grid') setGridSizeItem($thumb.parent(), gridSize, false, zoomVisual);
+ lastZoomVisual = zoomVisual;
+ /*
+ console.log('lastZoomVisual=' + lastZoomVisual + ', startZoomVisual=' + startZoomVisual +
+ ', img.height=' + $img.height() + ', img.width=' + $img.width() +
+ ', zoomBoxSize=' + zoomBoxSize + ', zoomVisual=' + zoomVisual);
+ */
+ }; // zoomSlide
+
+ // function called when the focus item is dragged
+ var dragEvent = function(event, ui) {
+ var $this = $(this);
+ var w = $this.parent().width();
+ var h = $this.parent().height();
+ var t = ui.position.top > 0 ? ui.position.top + ($this.width() / 2) : 0;
+ var l = ui.position.left > 0 ? ui.position.left + ($this.height() / 2) : 0;
+ var oldFocus = getFocus();
+ var newFocus = {
+ 'top': t > 0 ? ((t / h) * 100) : 0,
+ 'left': l > 0 ? ((l / w) * 100) : 0,
+ 'zoom': getFocusProperty('zoom')
+ };
+ setFocus(newFocus);
+ if(useZoomFocus) {
+ zoomSlide();
+ } else if(mode == 'grid') {
+ setGridSizeItem($thumb.parent(), gridSize, false);
+ }
+ }; // dragEvent
+
+ // make draggable and attach events
+ $focusCircle.draggable({
+ containment: 'parent',
+ drag: dragEvent,
+ stop: dragEvent
+ });
+
+ if(useZoomFocus) {
+ // setup the focus zoom slider
+ var zoom = getFocusProperty('zoom');
+ $zoomSlider = $("").addClass('focusZoomSlider').css({
+ 'margin-top': '5px'
+ });
+ $zoomBox = $("").addClass('focusZoomBox').css({
+ 'position': 'absolute',
+ 'background': 'rgba(0,0,0,0.5)',
+ 'box-shadow': '0 0 20px rgba(0,0,0,.9)'
+ });
+ $focusArea.prepend($zoomBox);
+ $img.after($zoomSlider);
+ $thumb.attr('src', $img.attr('src'));
+ $zoomSlider.slider({
+ min: 0,
+ max: 80,
+ value: zoom,
+ range: 'max',
+ slide: function(event, ui) {
+ zoomSlide(ui.value);
+ }
+ });
+ zoomSlide(zoom);
+ } else {
+ $focusArea.css('background-color', 'rgba(0,0,0,0.5)');
+ }
+
+ }
+
+ function stopFocus($edit) {
+ $focusCircle = $edit.find('.focusCircle');
+ if($focusCircle.length) {
+ var $focusWrap = $focusCircle.closest('.focusWrap');
+ $focusWrap.find('.focusZoomSlider').slider('destroy').remove();
+ $focusWrap.find('.focusZoomBox').remove();
+ $focusWrap.removeClass('focusWrap');
+ $focusCircle.draggable('destroy');
+ $focusCircle.parent().removeClass('focusActive');
+ $focusCircle.remove();
+ var $button = $edit.find('.InputfieldImageButtonFocus');
+ if($button.length) {
+ $icon = $button.find('i');
+ $icon.removeClass('focusIconActive').toggleClass($icon.attr('data-toggle'));
+ }
+ }
+ }
+
/**
* Tear down the InputfieldImageEdit panel
*
@@ -363,6 +609,8 @@ function InputfieldImage($) {
*
*/
function tearDownEdit($edit) {
+ stopFocus($edit);
+ $edit.off('click', '.InputfieldImageButtonFocus');
$inputArea = $edit.find(".InputfieldImageEdit__edit");
if($inputArea.children().not(".InputfieldFileSort").length) {
var $items = $inputArea.children();
@@ -517,7 +765,24 @@ function InputfieldImage($) {
};
$.magnificPopup.open(options);
return true;
- });
+
+ }).on('click', '.InputfieldImageButtonFocus', function() {
+
+ var $button = $(this);
+ var $icon = $button.find('i');
+ var $edit = $button.closest('.InputfieldImageEdit, .gridImage');
+ var $focusCircle = $edit.find('.focusCircle');
+
+ if($focusCircle.length) {
+ // stops focus
+ stopFocus($edit);
+ } else {
+ // starts focus
+ startFocus($edit);
+ $icon.addClass('focusIconActive');
+ $icon.toggleClass($icon.attr('data-toggle'));
+ }
+ });
$(document).on("click", function(e) {
var $el = $(e.target);
@@ -662,8 +927,14 @@ function InputfieldImage($) {
pct = Math.floor(pct);
$inputfield.find(".gridImage__overflow").each(function() {
var dataPct = 100 - pct;
- $(this).css('width', pct + '%');
- $(this).siblings('.ImageData').css('width', dataPct + '%');
+ var $this = $(this);
+ $this.css('width', pct + '%');
+ $this.siblings('.ImageData').css('width', dataPct + '%');
+ $this.find('img').css({
+ top: 0,
+ left: 0,
+ transform: 'none',
+ });
});
setCookieData($inputfield, 'listSize', pct);
}
@@ -718,9 +989,10 @@ function InputfieldImage($) {
* @param $item
* @param gridSize
* @param ragged
+ * @param zoom
*
*/
- function setGridSizeItem($item, gridSize, ragged) {
+ function setGridSizeItem($item, gridSize, ragged, zoom) {
if($item.hasClass('gridImage__overflow')) {
var $img = $item.children('img');
@@ -736,23 +1008,109 @@ function InputfieldImage($) {
$item.width('auto').height('auto');
return;
}
+
+ if(typeof zoom == "undefined") zoom = 0;
+ var focus = {};
var w = $img.width();
var h = $img.height();
- if(!w) w = parseInt($img.attr('data-w'));
- if(!h) h = parseInt($img.attr('data-h'));
+ var dataW = parseInt($img.attr('data-w'));
+ var dataH = parseInt($img.attr('data-h'));
+ if(!w) w = dataW;
+ if(!h) h = dataH;
+
+ if(!ragged) {
+ var focusStr = $img.attr('data-focus');
+ if(typeof focusStr == "undefined") focusStr = '50.0 50.0 0';
+ var focusArray = focusStr.split(' ');
+ focus = {
+ top: parseFloat(focusArray[0]),
+ left: parseFloat(focusArray[1]),
+ zoom: parseInt(focusArray[2])
+ };
+ }
if(ragged) {
- $img.css('max-height', '100%').css('max-width', 'none');
+ // show full thumbnail (not square)
$img.attr('height', gridSize).removeAttr('width');
+ $img.css({
+ 'max-height': '100%',
+ 'max-width': 'none',
+ 'top': '50%',
+ 'left': '50%',
+ 'transform': 'translate3d(-50%, -50%, 0)'
+ });
+
+ } else if(zoom > 0 && useZoomFocus) {
+ // focus with zoom
+ if(w >= h) {
+ $img.attr('height', gridSize).removeAttr('width');
+ var maxHeight = '100%';
+ var maxWidth = 'none';
+ } else {
+ var maxHeight = 'none';
+ var maxWidth = '100%';
+ $img.attr('width', gridSize).removeAttr('height');
+ }
+ var top = focus.top;
+ var left = focus.left;
+ var scale = 1 + (zoom / 25); //(zoom * 0.037);
+ if(scale < 0) scale = 0;
+ if(left < 1.0) left = 0.001;
+ if(top < 1.0) top = 0.001;
+ if(left >= 55) {
+ left += (left * 0.15);
+ } else if(left <= 45) {
+ left -= (left * 0.15);
+ }
+ if(top > 50) {
+ top += (top * 0.1);
+ } else if(top < 50) {
+ top -= (top * 0.1);
+ }
+ if(left > 100) left = 100;
+ if(top > 100) top = 100;
+ $img.css({
+ 'max-height': maxHeight,
+ 'max-width': maxWidth,
+ 'top': top + '%',
+ 'left': left + '%',
+ 'transform-origin': 'top left',
+ 'transform': 'scale(' + scale + ') translate3d(-' + (left) + '%, -' + (top) + '%, 0)'
+ });
+ // console.log("top=" + top + ", left=" + left + ", scale=" + scale);
+
} else if(w >= h) {
- $img.css('max-height', '100%').css('max-width', 'none');
+ // image width greater than height
$img.attr('height', gridSize).removeAttr('width');
+ if(focus.left < 1) focus.left = 0.001;
+ $img.css({
+ 'max-height': '100%',
+ 'max-width': 'none',
+ 'top': '50%',
+ 'left': focus.left + '%',
+ 'transform': 'translate3d(-' + focus.left + '%, -50%, 0)'
+ });
} else if(h > w) {
- $img.css('max-height', 'none').css('max-width', '100%');
+ // image height greater tahn width
$img.attr('width', gridSize).removeAttr('height');
+ if(focus.top < 1) focus.top = 0.001;
+ $img.css({
+ 'max-height': 'none',
+ 'max-width': '100%',
+ 'top': focus.top + '%',
+ 'left': '50%',
+ 'transform': 'translate3d(-50%, -' + focus.top + '%, 0)'
+ });
} else {
- $img.css('max-height', '100%').css('max-width', 'none');
+ // perfectly square image
+ $img.css({
+ 'max-height': '100%',
+ 'max-width': 'none',
+ 'top': '50%',
+ 'left': '50%',
+ 'transform': 'translate3d(-50%, -50%, 0)'
+ });
$img.removeAttr('width').attr('height', gridSize);
}
@@ -808,8 +1166,10 @@ function InputfieldImage($) {
var $inputfield = $a.closest('.Inputfield');
var href = $a.attr('href');
var size;
+ var $aPrev = $a.parent().children('.' + activeClass);
+ var hrefPrev = $aPrev.attr('href');
- $a.parent().children('.' + activeClass).removeClass(activeClass);
+ $aPrev.removeClass(activeClass);
$a.addClass(activeClass);
if(href == 'list') {
@@ -831,6 +1191,10 @@ function InputfieldImage($) {
size = getCookieData($inputfield, 'size');
setGridSize($inputfield, size, false);
setCookieData($inputfield, 'mode', 'grid');
+ if(hrefPrev == 'left') setTimeout(function() {
+ // because width/height aren't immediately available for img, so run again in this case
+ setGridSize($inputfield, size, false);
+ }, 100);
}
//hrefPrev = href; //hrefPrev == href && href != 'left' && href != 'list' ? '' : href;
diff --git a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.min.js b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.min.js
index 1e7f7e4e..bd352a31 100644
--- a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.min.js
+++ b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.min.js
@@ -1 +1 @@
-function InputfieldImage(v){var k=null;var b={file:"",item:null,edit:null};var F={type:"image",closeOnContentClick:true,closeBtnInside:true};var c=null;var r=[];function s(){var M=window.File&&window.FileList&&window.FileReader;var L=v(".InputfieldAllowAjaxUpload").length>0;var N=v("#PageIDIndicator").length>0;return(M&&(N||L))}function y(N,L,M){L||(L=250);var O,P;return function(){var S=M||this;var R=+new Date(),Q=arguments;if(O&&R .gridImage",start:function(Q,P){var O=E(M.closest(".Inputfield"),"size");P.placeholder.append(v("").css({display:"block",height:O+"px",width:O+"px"}));N=window.setTimeout(function(){G(M,null)},100);M.addClass("InputfieldImageSorting")},stop:function(Q,O){var P=v(this);if(N!==null){O.item.find(".InputfieldImageEdit__edit").click();clearTimeout(N)}P.children("li").each(function(S){var R=v(this).find(".InputfieldFileSort");if(R.val()!=S){R.val(S).change()}});M.removeClass("InputfieldImageSorting")},cancel:".InputfieldImageEdit,input,textarea,button,select,option"};M.sortable(L)}function p(M){var L=v.extend(true,{},F);L.callbacks={elementParse:function(N){var O=v(N.el).attr("data-original");if(typeof O=="undefined"||!O){O=v(N.el).attr("src")}N.src=O}};L.gallery={enabled:true};M.find("img").magnificPopup(L)}function t(M){var L=v.extend(true,{},F);L.callbacks={elementParse:function(N){N.src=v(N.el).attr("src")}};L.gallery={enabled:false};M.find("img").magnificPopup(L)}function C(L){return L.find(".InputfieldImageEdit--active")}function u(L){return v("#"+L.find(".InputfieldImageEdit__edit").attr("data-current"))}function D(N){var L=N.is(":checked");var M=N.parents(".gridImages").find(".gridImage__deletebox");if(L){M.prop("checked","checked").change()}else{M.removeAttr("checked").change()}}function J(M){if(typeof M=="undefined"){var L=v(".gridImages")}else{var L=M.find(".gridImages")}L.each(function(){var N=v(this),O=C(N);if(O.length){i(u(O),O)}})}function w(L){var O=[];var T=[];var Q=[];var N=0,S=0,M=0;var R;if(typeof L=="undefined"){R=v(".InputfieldImage.Inputfield")}else{R=L}R.removeClass("InputfieldImageNarrow InputfieldImageMedium InputfieldImageWide");R.each(function(){var V=v(this);var W=V.width();if(W<1){return}if(W<=500){O[N]=V;N++}else{if(W<=900){T[S]=V;S++}else{Q[M]=V;M++}}});for(var P=0;P=Q){O.css("max-height","100%").css("max-width","none");O.attr("height",M).removeAttr("width")}else{if(Q>L){O.css("max-height","none").css("max-width","100%");O.attr("width",M).removeAttr("height")}else{O.css("max-height","100%").css("max-width","none");O.removeAttr("width").attr("height",M)}}}var L=O.width();if(L){N.css({width:(P?L+"px":M+"px"),height:M+"px"})}else{var R=N.attr("data-tries");if(!R){R=0}if(typeof R=="undefined"){R=0}R=parseInt(R);if(R>3){N.css({width:M+"px",height:M+"px"})}else{r.push(N);N.attr("data-tries",R+1)}}}function A(M){if(M.find(".InputfieldImageListToggle").length){return}var P=v("").append("");var R=v("").append("");var L=v("").append("");var Q="InputfieldImageListToggle--active";var O="";var N=function(W){var V=v(this);var U=V.closest(".Inputfield");var S=V.attr("href");var T;V.parent().children("."+Q).removeClass(Q);V.addClass(Q);if(S=="list"){if(!U.hasClass("InputfieldImageEditAll")){U.find(".InputfieldImageEdit--active .InputfieldImageEdit__close").click();U.addClass("InputfieldImageEditAll")}T=E(U,"listSize");l(U,T);e(U,"mode","list")}else{if(S=="left"){U.removeClass("InputfieldImageEditAll");T=E(U,"size");j(U,T,true);e(U,"mode","left");J()}else{if(S=="grid"){U.removeClass("InputfieldImageEditAll");T=E(U,"size");j(U,T,false);e(U,"mode","grid")}}}B(U.find(".gridImages"));V.blur();return false};P.click(N);R.click(N);L.click(N);if(M.hasClass("InputfieldImage")){M.find(".InputfieldHeader").append(P).append(R).append(L);O=E(M,"mode")}else{v(".InputfieldImage .InputfieldHeader",M).append(P).append(R).append(L)}if(O=="list"){P.click()}else{if(O=="left"){R.click()}else{}}}function z(Q){var N=Q.children(".InputfieldHeader");if(N.children(".InputfieldImageSizeSlider").length){return}var P=Q.find(".gridImages");var M=P.attr("data-gridsize");var O=M/2;var L=M*2;var R=v('');N.append(R);R.slider({min:O,max:L,value:E(Q,"size"),range:"min",slide:function(U,W){var V=W.value;var X=15;var Y=Math.floor(M/X);var S=V-O;var T=Math.floor(X+(S/Y));if(Q.hasClass("InputfieldImageEditAll")){e(Q,"size",V);l(Q,T)}else{e(Q,"listSize",T);j(Q,V)}},start:function(S,T){if(Q.find(".InputfieldImageEdit:visible").length){Q.find(".InputfieldImageEdit__close").click()}},stop:function(S,T){J(Q)}})}function e(M,P,O){var N=E(M);var Q=M.attr("id");var L=Q?Q.replace("wrap_Inputfield_",""):"";if(!L.length||typeof O=="undefined"){return}if(N[L][P]==O){return}N[L][P]=O;v.cookie("InputfieldImage",N);c=N}function E(M,P){if(c&&typeof P=="undefined"){return c}var Q=M.attr("id");var L=Q?Q.replace("wrap_Inputfield_",""):"na";var O=c?c:v.cookie("InputfieldImage");var N=null;if(!O){var O={}}if(typeof O[L]=="undefined"){O[L]={}}if(typeof O[L].size=="undefined"){O[L].size=parseInt(M.find(".gridImages").attr("data-size"))}if(typeof O[L].listSize=="undefined"){O[L].listSize=23}if(typeof O[L].mode=="undefined"){O[L].mode=M.find(".gridImages").attr("data-gridMode")}if(c==null){c=O}if(typeof P=="undefined"){N=O}else{if(P===true){N=O[L]}else{if(typeof O[L][P]!="undefined"){N=O[L][P]}}}return N}function a(P){if(P.hasClass("InputfieldStateCollapsed")){return}var Q=parseInt(P.find(".InputfieldImageMaxFiles").val());var O=P.find(".gridImages");var N=E(P,"size");var R=E(P,"mode");var M=R=="left"?true:false;if(!N){N=O.attr("data-gridsize")}N=parseInt(N);if(P.hasClass("InputfieldImageEditAll")||R=="list"){var L=E(P,"listSize");l(P,L)}else{j(P,N,M)}if(!P.hasClass("InputfieldImageInit")){P.addClass("InputfieldImageInit");if(P.hasClass("InputfieldRenderValueMode")){return p(P)}else{if(Q==1){P.addClass("InputfieldImageMax1");t(P)}else{B(O)}}A(P);z(P)}w(P);P.on("change",".InputfieldFileActionSelect",function(){var S=v(this).next(".InputfieldFileActionNote");if(v(this).val().length){S.fadeIn()}else{S.hide()}})}function I(){v("body").addClass("ie-no-drop");v(".InputfieldImage.InputfieldFileMultiple").each(function(){var M=v(this),O=parseInt(M.find(".InputfieldFileMaxFiles").val()),L=M.find(".gridImages"),N=M.find(".InputfieldImageUpload");N.on("change","input[type=file]",function(){var S=v(this),Q=S.parent(".InputMask");if(S.val().length>1){Q.addClass("ui-state-disabled")}else{Q.removeClass("ui-state-disabled")}if(S.next("input.InputfieldFile").length>0){return}var P=L.children("li").length+N.find("input[type=file]").length+1;if(O>0&&P>=O){return}N.find(".InputMask").not(":last").each(function(){var T=v(this);if(T.find("input[type=file]").val()<1){T.remove()}});var R=Q.clone().removeClass("ui-state-disabled");R.children("input[type=file]").val("");R.insertAfter(Q)})})}function K(N){var M;if(N.length>0){M=N.find(".InputfieldImageUpload")}else{M=v(".InputfieldImageUpload")}M.each(function(Q){var R=v(this);var P=R.closest(".InputfieldContent");if(R.hasClass("InputfieldImageInitUpload")){return}O(P,Q);R.addClass("InputfieldImageInitUpload")});function O(Y,ak){var X=Y.parents("form");var P=Y.closest(".InputfieldRepeaterItem");var T=P.length?P.attr("data-editUrl"):X.attr("action");T+=(T.indexOf("?")>-1?"&":"?")+"InputfieldFileAjax=1";var ap=X.find("input._post_token");var W=ap.attr("name");var ab=ap.val();var aa=Y.find(".InputfieldImageErrors").first();var S=Y.find(".InputfieldImageUpload").data("fieldname");S=S.slice(0,-2);var ai=Y.closest(".Inputfield.InputfieldImage");var ao=Y.find(".InputfieldImageUpload").data("extensions").toLowerCase();var ah=Y.find(".InputfieldImageUpload").data("maxfilesize");var Z=Y.find("input[type=file]").get(0);var R=Y.find(".gridImages");var al=R.get(0);var ad=R.data("gridsize");var ae=null;var ac=parseInt(Y.find(".InputfieldImageMaxFiles").val());var an=n(ai);var aj=an.maxWidth>0||an.maxHeight>0||an.maxSize>0;am(Y);if(ac!=1){ag(R)}R.children().addClass("InputfieldFileItemExisting");ai.on("pwimageupload",function(aq,ar){af([ar.file],ar.xhr)});function V(ar,aq){if(typeof aq!=="undefined"){ar=""+aq+": "+ar}return""+ar+""}function Q(ar){var aq=new String(ar).substring(ar.lastIndexOf("/")+1);if(aq.lastIndexOf(".")!=-1){aq=aq.substring(0,aq.lastIndexOf("."))}return aq}function am(ar){if(ar.hasClass("InputfieldImageDropzoneInit")){return}var av=ar.get(0);var au=ar.closest(".Inputfield");function aq(){if(au.hasClass("pw-drag-in-file")){return}ar.addClass("ui-state-hover");au.addClass("pw-drag-in-file")}function at(){if(!au.hasClass("pw-drag-in-file")){return}ar.removeClass("ui-state-hover");au.removeClass("pw-drag-in-file")}av.addEventListener("dragleave",function(){at()},false);av.addEventListener("dragenter",function(aw){aw.preventDefault();aq()},false);av.addEventListener("dragover",function(aw){if(!ar.is("ui-state-hover")){aq()}aw.preventDefault();aw.stopPropagation();return false},false);av.addEventListener("drop",function(aw){af(aw.dataTransfer.files);at();aw.preventDefault();aw.stopPropagation();return false},false);ar.addClass("InputfieldImageDropzoneInit")}function ag(az){var aD=null;var aB=false;var ar=null;var aq=az.closest(".Inputfield");function aw(){aq.addClass("pw-drag-in-file")}function aC(){aq.removeClass("pw-drag-in-file")}function av(aF){var aJ=aF.offset();var aG=aF.width();var aE=aF.height();var aI=aJ.left+aG/2;var aH=aJ.top+aE/2;return{clientX:aI,clientY:aH}}function ay(){return az.find(".InputfieldImageEdit--active").length>0}function ax(aF){if(ay()){return}aF.preventDefault();aF.stopPropagation();aw();aB=false;if(aD==null){var aE=az.attr("data-size")+"px";var aG=v("").addClass("gridImage__overflow");if(az.closest(".InputfieldImageEditAll").length){aG.css({width:"100%",height:aE})}else{aG.css({width:aE,height:aE})}aD=v("").addClass("ImageOuter gridImage gridImagePlaceholder").append(aG);az.append(aD)}var aH=av(aD);aD.simulate("mousedown",aH)}function aA(aE){if(ay()){return}aE.preventDefault();aE.stopPropagation();aw();aB=false;if(aD==null){return}var aF={clientX:aE.originalEvent.clientX,clientY:aE.originalEvent.clientY};aD.simulate("mousemove",aF)}function au(aE){if(ay()){return}aE.preventDefault();aE.stopPropagation();if(aD==null){return false}aB=true;if(ar){clearTimeout(ar)}ar=setTimeout(function(){if(!aB||aD==null){return}aD.remove();aD=null;aC()},1000)}function at(aE){if(ay()){return}aC();aB=false;var aF={clientX:aE.clientX,clientY:aE.clientY};aD.simulate("mouseup",aF);k=aD.next(".gridImage");aD.remove();aD=null}if(az.length&&!az.hasClass("gridImagesInitDropInPlace")){az.on("dragenter",ax);az.on("dragover",aA);az.on("dragleave",au);az.on("drop",at);az.addClass("gridImagesInitDropInPlace")}}function U(aP,aD,az){var aM=ProcessWire.config.InputfieldImage.labels;var ax=parseInt(aP.size/1024,10)+" kB";var aO='";var aR=v(''),aJ=v(aO),ay=v(''),aq=v(''),aG=v(""),aI=v(""),aL=v(' '),aK=v('
'),ar,aB,aQ,aE=URL.createObjectURL(aP),at=ai.find(".gridImages"),av=ac==1,aH=E(ai,"size"),aw=E(ai,"listSize"),au=ai.hasClass("InputfieldImageEditAll"),aA=v('
');ay.append(aA);aG.find(".gridImage__inner").append(aL);aG.find(".gridImage__inner").append(aK.css("display","none"));aG.find(".gridImage__inner").append(aI);aq.append(v(''+ax+""));if(au){ay.css("width",aw+"%");aq.css("width",(100-aw)+"%")}else{ay.css({width:aH+"px",height:aH+"px"})}aR.append(aJ).append(ay).append(aG).append(aq);aA.attr({src:aE,"data-original":aE});img=new Image();img.addEventListener("load",function(){aJ.find(".dimensions").html(this.width+" × "+this.height);var aS=Math.min(this.width,this.height)/aH;aA.attr({width:this.width/aS,height:this.height/aS})},false);img.src=aE;if(typeof az!="undefined"){aB=az}else{aB=new XMLHttpRequest()}function aC(aS){if(typeof aS!="undefined"){if(!aS.lengthComputable){return}aI.attr("value",parseInt((aS.loaded/aS.total)*100))}v("body").addClass("pw-uploading");aK.css("display","block")}aB.upload.addEventListener("progress",aC,false);aB.addEventListener("load",function(){aB.getAllResponseHeaders();var aV=v.parseJSON(aB.responseText);if(typeof aV.ajaxResponse!="undefined"){aV=aV.ajaxResponse}var aT=aV.length>1;if(aV.error!==undefined){aV=[aV]}for(var aU=0;aU-1){aY=aY.substring(0,aY.indexOf("?"))}var aW=aY.substring(aY.lastIndexOf(".")+1).toLowerCase();aY=aY.substring(0,aY.lastIndexOf("."));if(aW==a2){a3.children("span").text(aY).removeAttr("contenteditable")}aX.find(".gridImage__edit").click()}b.file="";b.item=null;b.edit=null}if(ae){clearTimeout(ae)}k=null;ae=setTimeout(function(){if(ac!=1){B(at)}else{t(ai)}v("body").removeClass("pw-uploading");at.trigger("AjaxUploadDone")},500);ai.trigger("change").removeClass("InputfieldFileEmpty")},false);if(b.edit){b.edit.find(".InputfieldImageEdit__close").click()}else{if(ai.find(".InputfieldImageEdit:visible").length){ai.find(".InputfieldImageEdit__close").click()}}if(b.item){b.item.replaceWith(aR);b.item=aR}else{if(k&&k.length){k.before(aR)}else{at.append(aR)}}function aN(aS,aU){if(typeof az=="undefined"){aB.open("POST",T,true)}aB.setRequestHeader("X-FILENAME",encodeURIComponent(aS.name));aB.setRequestHeader("X-FIELDNAME",S);if(b.item){aB.setRequestHeader("X-REPLACENAME",b.file)}aB.setRequestHeader("Content-Type","application/octet-stream");aB.setRequestHeader("X-"+W,ab);aB.setRequestHeader("X-REQUESTED-WITH","XMLHttpRequest");if(typeof aU!="undefined"&&aU!=false){aB.send(aU)}else{aB.send(aS)}J();ai.trigger("change");var aT=ai.find(".InputfieldFileItem").length;if(aT==1){ai.removeClass("InputfieldFileEmpty").removeClass("InputfieldFileMultiple").addClass("InputfieldFileSingle")}else{if(aT>1){ai.removeClass("InputfieldFileEmpty").removeClass("InputfieldFileSingle").addClass("InputfieldFileMultiple")}}}aC();if(aj){var aF=new PWImageResizer(an);aK.addClass("pw-resizing");aF.resize(aP,function(aS){aK.removeClass("pw-resizing");aN(aP,aS)})}else{aN(aP)}}function af(aq,ay){var aw=function(aA){return parseInt(aA/1024,10)};if(typeof aq==="undefined"){al.innerHTML="No support for the File API in this web browser";return}for(var au=0,at=aq.length;auah&&ah>2000000){var ar=aw(aq[au].size),av=aw(ah);az="Filesize "+ar+" kb is too big. Maximum allowed is "+av+" kb";aa.append(V(az,aq[au].name))}else{if(typeof ay!="undefined"){U(aq[au],ax,ay)}else{U(aq[au],ax)}}}if(ac==1){break}}}Z.addEventListener("change",function(aq){af(this.files);aq.preventDefault();aq.stopPropagation();this.value=""},false)}function L(){var P=".InputfieldImageEdit__imagewrapper img";v(document).on("dragenter",P,function(){var S=v(this);if(S.closest(".InputfieldImageMax1").length){return}var T=S.attr("src");var Q=S.closest(".InputfieldImageEdit");var R=S.closest(".InputfieldImageEdit__imagewrapper");R.addClass("InputfieldImageEdit__replace");b.file=new String(T).substring(T.lastIndexOf("/")+1);b.item=v("#"+Q.attr("data-for"));b.edit=Q}).on("dragleave",P,function(){var R=v(this);if(R.closest(".InputfieldImageMax1").length){return}var Q=R.closest(".InputfieldImageEdit__imagewrapper");Q.removeClass("InputfieldImageEdit__replace");b.file="";b.item=null;b.edit=null})}L()}function n(M){var L={maxWidth:0,maxHeight:0,maxSize:0,quality:1,autoRotate:true,debug:ProcessWire.config.debug};var N=M.attr("data-resize");if(typeof N!="undefined"&&N.length){N=N.split(";");L.maxWidth=parseInt(N[0]);L.maxHeight=parseInt(N[1]);L.maxSize=parseFloat(N[2]);L.quality=parseFloat(N[3])}return L}function H(){v(".InputfieldImage.Inputfield").each(function(){a(v(this))});x();if(s()){K("")}else{I()}v(document).on("reloaded",".InputfieldImage",function(){var L=v(this);a(L);K(L)}).on("wiretabclick",function(N,M,L){M.find(".InputfieldImage").each(function(){a(v(this))})}).on("opened",".InputfieldImage",function(){a(v(this))})}H()}jQuery(document).ready(function(a){InputfieldImage(a)});
\ No newline at end of file
+function InputfieldImage(x){var l=null;var b={file:"",item:null,edit:null};var I={type:"image",closeOnContentClick:true,closeBtnInside:true};var c=null;var s=[];var k=false;function u(){var P=window.File&&window.FileList&&window.FileReader;var O=x(".InputfieldAllowAjaxUpload").length>0;var Q=x("#PageIDIndicator").length>0;return(P&&(Q||O))}function A(Q,O,P){O||(O=250);var R,S;return function(){var V=P||this;var U=+new Date(),T=arguments;if(R&&U .gridImage",start:function(T,S){var R=H(P.closest(".Inputfield"),"size");S.placeholder.append(x("").css({display:"block",height:R+"px",width:R+"px"}));Q=window.setTimeout(function(){J(P,null)},100);P.addClass("InputfieldImageSorting")},stop:function(T,R){var S=x(this);if(Q!==null){R.item.find(".InputfieldImageEdit__edit").click();clearTimeout(Q)}S.children("li").each(function(V){var U=x(this).find(".InputfieldFileSort");if(U.val()!=V){U.val(V).change()}});P.removeClass("InputfieldImageSorting")},cancel:".InputfieldImageEdit,.focusArea,input,textarea,button,select,option"};P.sortable(O)}function q(P){var O=x.extend(true,{},I);O.callbacks={elementParse:function(Q){var R=x(Q.el).attr("data-original");if(typeof R=="undefined"||!R){R=x(Q.el).attr("src")}Q.src=R}};O.gallery={enabled:true};P.find("img").magnificPopup(O)}function v(P){var O=x.extend(true,{},I);O.callbacks={elementParse:function(Q){Q.src=x(Q.el).attr("src")}};O.gallery={enabled:false};P.find("img").magnificPopup(O)}function F(O){return O.find(".InputfieldImageEdit--active")}function w(O){return x("#"+O.find(".InputfieldImageEdit__edit").attr("data-current"))}function G(Q){var O=Q.is(":checked");var P=Q.parents(".gridImages").find(".gridImage__deletebox");if(O){P.prop("checked","checked").change()}else{P.removeAttr("checked").change()}}function M(P){if(typeof P=="undefined"){var O=x(".gridImages")}else{var O=P.find(".gridImages")}O.each(function(){var Q=x(this),R=F(Q);if(R.length){i(w(R),R)}})}function y(O){var R=[];var W=[];var T=[];var Q=0,V=0,P=0;var U;if(typeof O=="undefined"){U=x(".InputfieldImage.Inputfield")}else{U=O}U.removeClass("InputfieldImageNarrow InputfieldImageMedium InputfieldImageWide");U.each(function(){var Y=x(this);var Z=Y.width();if(Z<1){return}if(Z<=500){R[Q]=Y;Q++}else{if(Z<=900){W[V]=Y;V++}else{T[P]=Y;P++}}});for(var S=0;S").addClass("focusArea");X.after(ai)}ai.css({height:X.height()+"px",width:X.width()+"px"}).addClass("focusActive");Z=ai.find(".focusCircle");if(!Z.length){Z=x("").addClass("focusCircle");ai.append(Z)}X.parent().addClass("focusWrap");ae();var af=function(av){if(typeof av=="undefined"){av=Q}Q=av;var ao=(100-av)+"%";V.width(ao);var an=V.width();var aq=Z.height();V.height(an);var ap=av;var au=parseInt(Z.css("top"));au+=Math.floor(aq/2);au-=Math.ceil(an/2)-3;var at=parseInt(Z.css("left"));at+=Math.floor(aq/2);at-=Math.ceil(an/2)-3;if(au<0){au=0}if(at<0){at=0}if(au+an>ai.height()){au=ai.height()-an}if(at+an>ai.width()){at=ai.width()-an}V.css({top:au+"px",left:at+"px"});aa("zoom",av);var ar=av;if(an>X.height()||an>X.width()){ar=0}else{if(!W){aj=ar}ar=(ar-aj)+1}if(ad=="grid"){h(ab.parent(),ac,false,ar)}W=ar};var Y=function(an,au){var at=x(this);var av=at.parent().width();var ar=at.parent().height();var aw=au.position.top>0?au.position.top+(at.width()/2):0;var aq=au.position.left>0?au.position.left+(at.height()/2):0;var ao=R();var ap={top:aw>0?((aw/ar)*100):0,left:aq>0?((aq/av)*100):0,zoom:U("zoom")};P(ap);if(k){af()}else{if(ad=="grid"){h(ab.parent(),ac,false)}}};Z.draggable({containment:"parent",drag:Y,stop:Y});if(k){var O=U("zoom");al=x("").addClass("focusZoomSlider").css({"margin-top":"5px"});V=x("").addClass("focusZoomBox").css({position:"absolute",background:"rgba(0,0,0,0.5)","box-shadow":"0 0 20px rgba(0,0,0,.9)"});ai.prepend(V);X.after(al);ab.attr("src",X.attr("src"));al.slider({min:0,max:80,value:O,range:"max",slide:function(an,ao){af(ao.value)}});af(O)}else{ai.css("background-color","rgba(0,0,0,0.5)")}}function C(O){$focusCircle=O.find(".focusCircle");if($focusCircle.length){var P=$focusCircle.closest(".focusWrap");P.find(".focusZoomSlider").slider("destroy").remove();P.find(".focusZoomBox").remove();P.removeClass("focusWrap");$focusCircle.draggable("destroy");$focusCircle.parent().removeClass("focusActive");$focusCircle.remove();var Q=O.find(".InputfieldImageButtonFocus");if(Q.length){$icon=Q.find("i");$icon.removeClass("focusIconActive").toggleClass($icon.attr("data-toggle"))}}}function n(O){C(O);O.off("click",".InputfieldImageButtonFocus");$inputArea=O.find(".InputfieldImageEdit__edit");if($inputArea.children().not(".InputfieldFileSort").length){var P=$inputArea.children();x("#"+$inputArea.attr("data-current")).find(".ImageData").append(P)}}function J(Q,P){var O;if(Q){O=Q.find(".InputfieldImageEdit--active")}else{if(P){O=x(".InputfieldImageEdit--active").not(P.find(".InputfieldImageEdit--active"))}else{O=x(".InputfieldImageEdit--active")}}if(O.length){n(O);O.removeClass("InputfieldImageEdit--active").removeAttr("id");x("#"+O.attr("data-for")).removeClass("gridImageEditing")}x(".InputfieldImageEdit__replace").removeClass("InputfieldImageEdit__replace")}function i(R,P){if(!R||!R.length){return}var O=R.parent().children().not(".InputfieldImageEdit");var S=0;var T=false;var U=null;O.each(function(){if(U){return}var V=x(this);var W=V.offset().top;if(T&&W!=S){U=V}else{if(V.attr("id")==R.attr("id")){T=true}}S=W});if(U){P.insertBefore(U)}else{P.insertAfter(O.eq(O.length-1))}var Q=P.find(".InputfieldImageEdit__arrow");if(Q.length){Q.css("left",R.position().left+(R.outerWidth()/2)+"px")}}function z(){x(window).resize(A(g,200));x(document).on("click dblclick",".gridImage__trash",function(O){var P=x(this).find("input");P.prop("checked",f).change();if(O.type=="dblclick"){G(P);O.preventDefault();O.stopPropagation()}});x(document).on("change",".gridImage__deletebox",function(){p(x(this))});x(document).on("click",".gridImage__edit",function(R){var P=x(this).closest(".gridImage");if(!P.length){return}if(P.closest(".InputfieldImageEditAll").length){return false}var Q=P.closest(".gridImages");var O=Q.find(".InputfieldImageEdit");if(P.hasClass("gridImageEditing")){O.find(".InputfieldImageEdit__close").click()}else{i(P,O);n(O);d(P,O);O.addClass("InputfieldImageEdit--active").attr("data-for",P.attr("id"));Q.find(".gridImageEditing").removeClass("gridImageEditing");P.addClass("gridImageEditing")}}).on("click",".InputfieldImageEditAll img",function(Q){Q.stopPropagation();Q.preventDefault();x.magnificPopup.close();var O=x.extend(true,{},I);var P=x(this);O.items={src:P.attr("data-original"),title:P.attr("alt")};x.magnificPopup.open(O);return true}).on("click",".InputfieldImageButtonFocus",function(){var R=x(this);var O=R.find("i");var P=R.closest(".InputfieldImageEdit, .gridImage");var Q=P.find(".focusCircle");if(Q.length){C(P)}else{t(P);O.addClass("focusIconActive");O.toggleClass(O.attr("data-toggle"))}});x(document).on("click",function(P){var O=x(P.target);if(O.closest(".InputfieldImageEdit").length){J(null,O.parents(".gridImages"))}else{if(O.is("input, textarea")&&O.closest(".InputfieldImageEditAll").length){O.focus().one("blur",function(){O.closest(".gridImages").sortable("enable")});O.closest(".gridImages").sortable("disable")}else{if(O.closest(".gridImage__inner").length){J(null,O.parents(".gridImages"))}else{if(O.closest(".mfp-container").length){return}else{if(O.closest(".ui-dialog").length){return}else{if(O.is(".mfp-close")){return}else{if(O.is("a.remove")){return}else{J(null,null)}}}}}}}});x(document).on("click",".InputfieldImageEdit__close",function(O){J(x(this).parents(".gridImages"),null)});x(document).on("change",".InputfieldImage",function(){x(this).find(".InputfieldImageButtonCrop:not(.pw-modal-dblclick)").addClass("pw-modal-dblclick ui-state-disabled")}).on("click",".InputfieldImageButtonCrop.ui-state-disabled",function(Q){var P=x(this);var O=P.closest(".gridImages");if(!O.hasClass("gridImagesAlerted")){ProcessWire.alert(ProcessWire.config.InputfieldImage.labels.changes);O.addClass("gridImagesAlerted")}setTimeout(function(){P.removeClass("ui-state-active")},500);return false});x(".ImagesGrid").on("click","button.pw-modal",function(O){O.preventDefault()});r();y()}function r(){x(document).on("click",".InputfieldImageEdit__name",function(Q){var O=x(this).children("span");var R=O.closest(".gridImage, .InputfieldImageEdit").find(".InputfieldFileRename");var P=O.closest(".gridImages");P.sortable("disable");R.val(O.text());O.on("keypress",function(S){if(S.which==13){O.blur();return false}return true});O.attr("autocomplete","off").attr("autocorrect","off").attr("autocapitalize","off").attr("spellcheck","false");O.focus().on("blur",function(){var S=x(this).text();if(x.trim(S).length<1){O.text(R.val())}else{if(S!=R.val()){R.val(S).change();P.closest(".Inputfield").trigger("change")}}O.off("keypress");P.sortable("enable")})})}function m(O,P){P=Math.floor(P);O.find(".gridImage__overflow").each(function(){var Q=100-P;var R=x(this);R.css("width",P+"%");R.siblings(".ImageData").css("width",Q+"%");R.find("img").css({top:0,left:0,transform:"none"})});e(O,"listSize",P)}function j(S,O,Q){if(!O){return}var P=O+"px";var R=S.find(".gridImages");if(typeof Q=="undefined"||Q==null){Q=R.attr("data-ragged")?true:false}if(Q){R.attr("data-ragged",1)}else{R.removeAttr("data-ragged")}R.find(".gridImage__overflow").each(function(){h(x(this),O,Q)});R.find(".gridImage__edit, .gridImage__resize").css("line-height",P);R.attr("data-size",O);e(S,"size",O);if(s.length){setTimeout(function(){while(s.length){var T=s.pop();h(T,O,Q)}},150)}}function h(P,T,S,O){if(P.hasClass("gridImage__overflow")){var R=P.children("img")}else{if(P.is("img")){var R=P;P=R.closest(".gridImage__overflow")}else{return}}if(!T){R.removeAttr("width").removeAttr("height");P.width("auto").height("auto");return}if(typeof O=="undefined"){O=0}var Y={};var U=R.width();var ac=R.height();var V=parseInt(R.attr("data-w"));var ad=parseInt(R.attr("data-h"));if(!U){U=V}if(!ac){ac=ad}if(!S){var Z=R.attr("data-focus");if(typeof Z=="undefined"){Z="50.0 50.0 0"}var ab=Z.split(" ");Y={top:parseFloat(ab[0]),left:parseFloat(ab[1]),zoom:parseInt(ab[2])}}if(S){R.attr("height",T).removeAttr("width");R.css({"max-height":"100%","max-width":"none",top:"50%",left:"50%",transform:"translate3d(-50%, -50%, 0)"})}else{if(O>0&&k){if(U>=ac){R.attr("height",T).removeAttr("width");var aa="100%";var ae="none"}else{var aa="none";var ae="100%";R.attr("width",T).removeAttr("height")}var X=Y.top;var Q=Y.left;var af=1+(O/25);if(af<0){af=0}if(Q<1){Q=0.001}if(X<1){X=0.001}if(Q>=55){Q+=(Q*0.15)}else{if(Q<=45){Q-=(Q*0.15)}}if(X>50){X+=(X*0.1)}else{if(X<50){X-=(X*0.1)}}if(Q>100){Q=100}if(X>100){X=100}R.css({"max-height":aa,"max-width":ae,top:X+"%",left:Q+"%","transform-origin":"top left",transform:"scale("+af+") translate3d(-"+(Q)+"%, -"+(X)+"%, 0)"})}else{if(U>=ac){R.attr("height",T).removeAttr("width");if(Y.left<1){Y.left=0.001}R.css({"max-height":"100%","max-width":"none",top:"50%",left:Y.left+"%",transform:"translate3d(-"+Y.left+"%, -50%, 0)"})}else{if(ac>U){R.attr("width",T).removeAttr("height");if(Y.top<1){Y.top=0.001}R.css({"max-height":"none","max-width":"100%",top:Y.top+"%",left:"50%",transform:"translate3d(-50%, -"+Y.top+"%, 0)"})}else{R.css({"max-height":"100%","max-width":"none",top:"50%",left:"50%",transform:"translate3d(-50%, -50%, 0)"});R.removeAttr("width").attr("height",T)}}}}var U=R.width();if(U){P.css({width:(S?U+"px":T+"px"),height:T+"px"})}else{var W=P.attr("data-tries");if(!W){W=0}if(typeof W=="undefined"){W=0}W=parseInt(W);if(W>3){P.css({width:T+"px",height:T+"px"})}else{s.push(P);P.attr("data-tries",W+1)}}}function D(P){if(P.find(".InputfieldImageListToggle").length){return}var S=x("").append("");var U=x("").append("");var O=x("").append("");var T="InputfieldImageListToggle--active";var R="";var Q=function(aa){var Z=x(this);var X=Z.closest(".Inputfield");var V=Z.attr("href");var W;var ab=Z.parent().children("."+T);var Y=ab.attr("href");ab.removeClass(T);Z.addClass(T);if(V=="list"){if(!X.hasClass("InputfieldImageEditAll")){X.find(".InputfieldImageEdit--active .InputfieldImageEdit__close").click();X.addClass("InputfieldImageEditAll")}W=H(X,"listSize");m(X,W);e(X,"mode","list")}else{if(V=="left"){X.removeClass("InputfieldImageEditAll");W=H(X,"size");j(X,W,true);e(X,"mode","left");M()}else{if(V=="grid"){X.removeClass("InputfieldImageEditAll");W=H(X,"size");j(X,W,false);e(X,"mode","grid");if(Y=="left"){setTimeout(function(){j(X,W,false)},100)}}}}E(X.find(".gridImages"));Z.blur();return false};S.click(Q);U.click(Q);O.click(Q);if(P.hasClass("InputfieldImage")){P.find(".InputfieldHeader").append(S).append(U).append(O);R=H(P,"mode")}else{x(".InputfieldImage .InputfieldHeader",P).append(S).append(U).append(O)}if(R=="list"){S.click()}else{if(R=="left"){U.click()}else{}}}function B(T){var Q=T.children(".InputfieldHeader");if(Q.children(".InputfieldImageSizeSlider").length){return}var S=T.find(".gridImages");var P=S.attr("data-gridsize");var R=P/2;var O=P*2;var U=x('');Q.append(U);U.slider({min:R,max:O,value:H(T,"size"),range:"min",slide:function(X,Z){var Y=Z.value;var aa=15;var ab=Math.floor(P/aa);var V=Y-R;var W=Math.floor(aa+(V/ab));if(T.hasClass("InputfieldImageEditAll")){e(T,"size",Y);m(T,W)}else{e(T,"listSize",W);j(T,Y)}},start:function(V,W){if(T.find(".InputfieldImageEdit:visible").length){T.find(".InputfieldImageEdit__close").click()}},stop:function(V,W){M(T)}})}function e(P,S,R){var Q=H(P);var T=P.attr("id");var O=T?T.replace("wrap_Inputfield_",""):"";if(!O.length||typeof R=="undefined"){return}if(Q[O][S]==R){return}Q[O][S]=R;x.cookie("InputfieldImage",Q);c=Q}function H(P,S){if(c&&typeof S=="undefined"){return c}var T=P.attr("id");var O=T?T.replace("wrap_Inputfield_",""):"na";var R=c?c:x.cookie("InputfieldImage");var Q=null;if(!R){var R={}}if(typeof R[O]=="undefined"){R[O]={}}if(typeof R[O].size=="undefined"){R[O].size=parseInt(P.find(".gridImages").attr("data-size"))}if(typeof R[O].listSize=="undefined"){R[O].listSize=23}if(typeof R[O].mode=="undefined"){R[O].mode=P.find(".gridImages").attr("data-gridMode")}if(c==null){c=R}if(typeof S=="undefined"){Q=R}else{if(S===true){Q=R[O]}else{if(typeof R[O][S]!="undefined"){Q=R[O][S]}}}return Q}function a(S){if(S.hasClass("InputfieldStateCollapsed")){return}var T=parseInt(S.find(".InputfieldImageMaxFiles").val());var R=S.find(".gridImages");var Q=H(S,"size");var U=H(S,"mode");var P=U=="left"?true:false;if(!Q){Q=R.attr("data-gridsize")}Q=parseInt(Q);if(S.hasClass("InputfieldImageEditAll")||U=="list"){var O=H(S,"listSize");m(S,O)}else{j(S,Q,P)}if(!S.hasClass("InputfieldImageInit")){S.addClass("InputfieldImageInit");if(S.hasClass("InputfieldRenderValueMode")){return q(S)}else{if(T==1){S.addClass("InputfieldImageMax1");v(S)}else{E(R)}}D(S);B(S)}y(S);S.on("change",".InputfieldFileActionSelect",function(){var V=x(this).next(".InputfieldFileActionNote");if(x(this).val().length){V.fadeIn()}else{V.hide()}})}function L(){x("body").addClass("ie-no-drop");x(".InputfieldImage.InputfieldFileMultiple").each(function(){var P=x(this),R=parseInt(P.find(".InputfieldFileMaxFiles").val()),O=P.find(".gridImages"),Q=P.find(".InputfieldImageUpload");Q.on("change","input[type=file]",function(){var V=x(this),T=V.parent(".InputMask");if(V.val().length>1){T.addClass("ui-state-disabled")}else{T.removeClass("ui-state-disabled")}if(V.next("input.InputfieldFile").length>0){return}var S=O.children("li").length+Q.find("input[type=file]").length+1;if(R>0&&S>=R){return}Q.find(".InputMask").not(":last").each(function(){var W=x(this);if(W.find("input[type=file]").val()<1){W.remove()}});var U=T.clone().removeClass("ui-state-disabled");U.children("input[type=file]").val("");U.insertAfter(T)})})}function N(Q){var P;if(Q.length>0){P=Q.find(".InputfieldImageUpload")}else{P=x(".InputfieldImageUpload")}P.each(function(T){var U=x(this);var S=U.closest(".InputfieldContent");if(U.hasClass("InputfieldImageInitUpload")){return}R(S,T);U.addClass("InputfieldImageInitUpload")});function R(ab,an){var aa=ab.parents("form");var S=ab.closest(".InputfieldRepeaterItem");var W=S.length?S.attr("data-editUrl"):aa.attr("action");W+=(W.indexOf("?")>-1?"&":"?")+"InputfieldFileAjax=1";var at=aa.find("input._post_token");var Z=at.attr("name");var ae=at.val();var ad=ab.find(".InputfieldImageErrors").first();var V=ab.find(".InputfieldImageUpload").data("fieldname");V=V.slice(0,-2);var al=ab.closest(".Inputfield.InputfieldImage");var ar=ab.find(".InputfieldImageUpload").data("extensions").toLowerCase();var ak=ab.find(".InputfieldImageUpload").data("maxfilesize");var ac=ab.find("input[type=file]").get(0);var U=ab.find(".gridImages");var ao=U.get(0);var ag=U.data("gridsize");var ah=null;var af=parseInt(ab.find(".InputfieldImageMaxFiles").val());var aq=o(al);var am=aq.maxWidth>0||aq.maxHeight>0||aq.maxSize>0;ap(ab);if(af!=1){aj(U)}U.children().addClass("InputfieldFileItemExisting");al.on("pwimageupload",function(au,av){ai([av.file],av.xhr)});function Y(av,au){if(typeof au!=="undefined"){av=""+au+": "+av}return""+av+""}function T(av){var au=new String(av).substring(av.lastIndexOf("/")+1);if(au.lastIndexOf(".")!=-1){au=au.substring(0,au.lastIndexOf("."))}return au}function ap(av){if(av.hasClass("InputfieldImageDropzoneInit")){return}var ay=av.get(0);var ax=av.closest(".Inputfield");function au(){if(ax.hasClass("pw-drag-in-file")){return}av.addClass("ui-state-hover");ax.addClass("pw-drag-in-file")}function aw(){if(!ax.hasClass("pw-drag-in-file")){return}av.removeClass("ui-state-hover");ax.removeClass("pw-drag-in-file")}ay.addEventListener("dragleave",function(){aw()},false);ay.addEventListener("dragenter",function(az){az.preventDefault();au()},false);ay.addEventListener("dragover",function(az){if(!av.is("ui-state-hover")){au()}az.preventDefault();az.stopPropagation();return false},false);ay.addEventListener("drop",function(az){ai(az.dataTransfer.files);aw();az.preventDefault();az.stopPropagation();return false},false);av.addClass("InputfieldImageDropzoneInit")}function aj(aC){var aG=null;var aE=false;var av=null;var au=aC.closest(".Inputfield");function az(){au.addClass("pw-drag-in-file")}function aF(){au.removeClass("pw-drag-in-file")}function ay(aI){var aM=aI.offset();var aJ=aI.width();var aH=aI.height();var aL=aM.left+aJ/2;var aK=aM.top+aH/2;return{clientX:aL,clientY:aK}}function aB(){return aC.find(".InputfieldImageEdit--active").length>0}function aA(aI){if(aB()){return}aI.preventDefault();aI.stopPropagation();az();aE=false;if(aG==null){var aH=aC.attr("data-size")+"px";var aJ=x("").addClass("gridImage__overflow");if(aC.closest(".InputfieldImageEditAll").length){aJ.css({width:"100%",height:aH})}else{aJ.css({width:aH,height:aH})}aG=x("").addClass("ImageOuter gridImage gridImagePlaceholder").append(aJ);aC.append(aG)}var aK=ay(aG);aG.simulate("mousedown",aK)}function aD(aH){if(aB()){return}aH.preventDefault();aH.stopPropagation();az();aE=false;if(aG==null){return}var aI={clientX:aH.originalEvent.clientX,clientY:aH.originalEvent.clientY};aG.simulate("mousemove",aI)}function ax(aH){if(aB()){return}aH.preventDefault();aH.stopPropagation();if(aG==null){return false}aE=true;if(av){clearTimeout(av)}av=setTimeout(function(){if(!aE||aG==null){return}aG.remove();aG=null;aF()},1000)}function aw(aH){if(aB()){return}aF();aE=false;var aI={clientX:aH.clientX,clientY:aH.clientY};aG.simulate("mouseup",aI);l=aG.next(".gridImage");aG.remove();aG=null}if(aC.length&&!aC.hasClass("gridImagesInitDropInPlace")){aC.on("dragenter",aA);aC.on("dragover",aD);aC.on("dragleave",ax);aC.on("drop",aw);aC.addClass("gridImagesInitDropInPlace")}}function X(aS,aG,aC){var aP=ProcessWire.config.InputfieldImage.labels;var aA=parseInt(aS.size/1024,10)+" kB";var aR='";var aU=x(''),aM=x(aR),aB=x(''),au=x(''),aJ=x(""),aL=x(""),aO=x(' '),aN=x('
'),av,aE,aT,aH=URL.createObjectURL(aS),aw=al.find(".gridImages"),ay=af==1,aK=H(al,"size"),az=H(al,"listSize"),ax=al.hasClass("InputfieldImageEditAll"),aD=x('
');aB.append(aD);aJ.find(".gridImage__inner").append(aO);aJ.find(".gridImage__inner").append(aN.css("display","none"));aJ.find(".gridImage__inner").append(aL);au.append(x(''+aA+""));if(ax){aB.css("width",az+"%");au.css("width",(100-az)+"%")}else{aB.css({width:aK+"px",height:aK+"px"})}aU.append(aM).append(aB).append(aJ).append(au);aD.attr({src:aH,"data-original":aH});img=new Image();img.addEventListener("load",function(){aM.find(".dimensions").html(this.width+" × "+this.height);var aV=Math.min(this.width,this.height)/aK;aD.attr({width:this.width/aV,height:this.height/aV})},false);img.src=aH;if(typeof aC!="undefined"){aE=aC}else{aE=new XMLHttpRequest()}function aF(aV){if(typeof aV!="undefined"){if(!aV.lengthComputable){return}aL.attr("value",parseInt((aV.loaded/aV.total)*100))}x("body").addClass("pw-uploading");aN.css("display","block")}aE.upload.addEventListener("progress",aF,false);aE.addEventListener("load",function(){aE.getAllResponseHeaders();var aY=x.parseJSON(aE.responseText);if(typeof aY.ajaxResponse!="undefined"){aY=aY.ajaxResponse}var aW=aY.length>1;if(aY.error!==undefined){aY=[aY]}for(var aX=0;aX-1){a1=a1.substring(0,a1.indexOf("?"))}var aZ=a1.substring(a1.lastIndexOf(".")+1).toLowerCase();a1=a1.substring(0,a1.lastIndexOf("."));if(aZ==a5){a6.children("span").text(a1).removeAttr("contenteditable")}a0.find(".gridImage__edit").click()}b.file="";b.item=null;b.edit=null}if(ah){clearTimeout(ah)}l=null;ah=setTimeout(function(){if(af!=1){E(aw)}else{v(al)}x("body").removeClass("pw-uploading");aw.trigger("AjaxUploadDone")},500);al.trigger("change").removeClass("InputfieldFileEmpty")},false);if(b.edit){b.edit.find(".InputfieldImageEdit__close").click()}else{if(al.find(".InputfieldImageEdit:visible").length){al.find(".InputfieldImageEdit__close").click()}}if(b.item){b.item.replaceWith(aU);b.item=aU}else{if(l&&l.length){l.before(aU)}else{aw.append(aU)}}function aQ(aV,aX){if(typeof aC=="undefined"){aE.open("POST",W,true)}aE.setRequestHeader("X-FILENAME",encodeURIComponent(aV.name));aE.setRequestHeader("X-FIELDNAME",V);if(b.item){aE.setRequestHeader("X-REPLACENAME",b.file)}aE.setRequestHeader("Content-Type","application/octet-stream");aE.setRequestHeader("X-"+Z,ae);aE.setRequestHeader("X-REQUESTED-WITH","XMLHttpRequest");if(typeof aX!="undefined"&&aX!=false){aE.send(aX)}else{aE.send(aV)}M();al.trigger("change");var aW=al.find(".InputfieldFileItem").length;if(aW==1){al.removeClass("InputfieldFileEmpty").removeClass("InputfieldFileMultiple").addClass("InputfieldFileSingle")}else{if(aW>1){al.removeClass("InputfieldFileEmpty").removeClass("InputfieldFileSingle").addClass("InputfieldFileMultiple")}}}aF();if(am){var aI=new PWImageResizer(aq);aN.addClass("pw-resizing");aI.resize(aS,function(aV){aN.removeClass("pw-resizing");aQ(aS,aV)})}else{aQ(aS)}}function ai(au,aB){var az=function(aD){return parseInt(aD/1024,10)};if(typeof au==="undefined"){ao.innerHTML="No support for the File API in this web browser";return}for(var ax=0,aw=au.length;axak&&ak>2000000){var av=az(au[ax].size),ay=az(ak);aC="Filesize "+av+" kb is too big. Maximum allowed is "+ay+" kb";ad.append(Y(aC,au[ax].name))}else{if(typeof aB!="undefined"){X(au[ax],aA,aB)}else{X(au[ax],aA)}}}if(af==1){break}}}ac.addEventListener("change",function(au){ai(this.files);au.preventDefault();au.stopPropagation();this.value=""},false)}function O(){var S=".InputfieldImageEdit__imagewrapper img";x(document).on("dragenter",S,function(){var V=x(this);if(V.closest(".InputfieldImageMax1").length){return}var W=V.attr("src");var T=V.closest(".InputfieldImageEdit");var U=V.closest(".InputfieldImageEdit__imagewrapper");U.addClass("InputfieldImageEdit__replace");b.file=new String(W).substring(W.lastIndexOf("/")+1);b.item=x("#"+T.attr("data-for"));b.edit=T}).on("dragleave",S,function(){var U=x(this);if(U.closest(".InputfieldImageMax1").length){return}var T=U.closest(".InputfieldImageEdit__imagewrapper");T.removeClass("InputfieldImageEdit__replace");b.file="";b.item=null;b.edit=null})}O()}function o(P){var O={maxWidth:0,maxHeight:0,maxSize:0,quality:1,autoRotate:true,debug:ProcessWire.config.debug};var Q=P.attr("data-resize");if(typeof Q!="undefined"&&Q.length){Q=Q.split(";");O.maxWidth=parseInt(Q[0]);O.maxHeight=parseInt(Q[1]);O.maxSize=parseFloat(Q[2]);O.quality=parseFloat(Q[3])}return O}function K(){x(".InputfieldImage.Inputfield").each(function(){a(x(this))});z();if(u()){N("")}else{L()}x(document).on("reloaded",".InputfieldImage",function(){var O=x(this);a(O);N(O)}).on("wiretabclick",function(Q,P,O){P.find(".InputfieldImage").each(function(){a(x(this))})}).on("opened",".InputfieldImage",function(){a(x(this))})}K()}jQuery(document).ready(function(a){InputfieldImage(a)});
\ No newline at end of file
diff --git a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.module b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.module
index 5bd2fa4e..28147c90 100755
--- a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.module
+++ b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.module
@@ -52,7 +52,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
return array(
'title' => __('Images', __FILE__), // Module Title
'summary' => __('One or more image uploads (sortable)', __FILE__), // Module Summary
- 'version' => 121,
+ 'version' => 122,
'permanent' => true,
);
}
@@ -125,6 +125,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
$this->labels = array_merge($this->labels, array(
'crop' => $this->_('Crop'),
+ 'focus' => $this->_('Focus'),
'variations' => $this->_('Variations'),
'dimensions' => $this->_('Dimensions'),
'filesize' => $this->_('Filesize'),
@@ -144,6 +145,11 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
$themeSettings = is_array($themeSettings) ? array_merge($themeDefaults, $themeSettings) : $themeDefaults;
$this->themeSettings = array_merge($this->themeSettings, $themeSettings);
}
+
+ public function get($key) {
+ if($key == 'themeSettings') return $this->themeSettings;
+ return parent::get($key);
+ }
/**
* Called right before Inputfield render
@@ -186,11 +192,14 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
}
if(!$renderValueMode && $this->value instanceof Pageimages) {
- $process = $this->wire('process');
- if($process instanceof WirePageEditor) {
- $page = $process->getPage();
- } else {
- $page = new NullPage();
+ $page = $this->hasPage;
+ if(!$page || !$page->id) {
+ $process = $this->wire('process');
+ if($process instanceof WirePageEditor) {
+ $page = $process->getPage();
+ } else {
+ $page = new NullPage();
+ }
}
if($page->id && $this->wire('user')->hasPermission('page-edit-images', $page)) {
$modules->get('JqueryUI')->use('modal');
@@ -250,6 +259,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
if(!$this->renderValueMode) {
$dropNew = $this->wire('sanitizer')->entities1($this->_('drop in new image file to replace'));
+ $focus = $this->wire('sanitizer')->entities1($this->_('drag circle to center of focus'));
$out .= "
@@ -258,7 +268,8 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
@@ -474,6 +485,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
$imageSizerOptions = $this->imageSizerOptions;
$imageSizerOptions['upscaling'] = true;
+ $imageSizerOptions['focus'] = false; // disable focus since we show focus from JS/CSS in admin thumbs
$adminThumbOptions = $this->wire('config')->adminThumbOptions;
$gridSize2x = $this->gridSize * 2;
// if($adminThumbOptions['scale'] === 1.0) $gridSize2x = $this->gridSize; // force non-HiDPI
@@ -542,6 +554,10 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
$attr['data-w'] = $_thumbWidth;
$attr['data-h'] = $_thumbHeight;
$attr["data-original"] = $img->URL;
+
+ $focus = $img->focus();
+ $attr['data-focus'] = $focus['str'];
+
$markup = "
![]()
$value) $markup .= "$key=\"$value\" ";
$markup .= " />";
@@ -559,6 +575,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
'error' => $error,
'title' => $title,
);
+
return $a;
}
@@ -639,6 +656,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
// @todo move the following to separate method shared by the renderSingle
$ext = $pagefile->ext();
$basename = $pagefile->basename(false);
+ $focus = $pagefile->focus();
$out .= "
@@ -651,6 +669,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
+
";
}
@@ -744,16 +763,28 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
$variations = $this->getPagefileVariations($pagefile);
$variationCount = count($variations);
$editUrl = $this->getEditUrl($pagefile, $pageID);
- $variationUrl = $this->getVariationUrl($pagefile, $id);
- $buttonClass = $this->themeSettings['buttonClass'] . " $this->modalClass pw-modal";
+ $variationUrl = $this->getVariationUrl($pagefile, $id);
+ $buttonClass = $this->themeSettings['buttonClass'];
+ $modalButtonClass = trim("$buttonClass $this->modalClass pw-modal");
$modalAttrs = "data-buttons='#non_rte_dialog_buttons button' data-autoclose='1' data-close='#non_rte_cancel'";
$labels = $this->labels;
+ $out = '';
- $buttonText = str_replace('{out}', "
$labels[crop]", $this->themeSettings['buttonText']);
- $out = "
";
- $buttonText = "
$labels[variations]
($variationCount)";
+
+ // Crop
+ $buttonText = str_replace('{out}', "
$labels[crop]", $this->themeSettings['buttonText']);
+ $out .= "
";
+
+ // Focus
+ $iconA = $pagefile->hasFocus ? 'fa-check-circle-o' : 'fa-circle-o';
+ $iconB = $pagefile->hasFocus ? 'fa-check-circle' : 'fa-dot-circle-o';
+ $buttonText = str_replace('{out}', "
$labels[focus]", $this->themeSettings['buttonText']);
+ $out .= "
";
+
+ // Variations
+ $buttonText = "
$labels[variations]
($variationCount)";
$buttonText = str_replace('{out}', $buttonText, $this->themeSettings['buttonText']);
- $out .= "
";
+ $out .= "
";
return $out;
}
@@ -1262,6 +1293,45 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
return $this;
}
+ /**
+ * Process input for a given Pageimage
+ *
+ * @param WireInputData $input
+ * @param Pagefile|Pageimage $pagefile
+ * @param int $n
+ * @return bool
+ *
+ */
+ protected function ___processInputFile(WireInputData $input, Pagefile $pagefile, $n) {
+
+ $changed = false;
+
+ $id = $this->name . '_' . $pagefile->hash;
+ $key = "focus_$id";
+ $val = $input->$key;
+
+ if($val !== null) {
+ if(!strlen($val)) $val = '50 50 0';
+ $focus = $pagefile->focus();
+ if($focus['str'] !== $val) {
+ $pagefile->focus($val);
+ $changed = true;
+ $focus = $pagefile->focus();
+ $rebuild = $pagefile->rebuildVariations();
+ // @todo rebuild variations only for images that specify both width and height
+ $this->message(
+ "Updated focus for $pagefile to: top=$focus[top]%, left=$focus[left]%, zoom=$focus[zoom] " .
+ "and rebuilt " . count($rebuild['rebuilt']) . " variations",
+ Notice::debug
+ );
+ }
+ }
+
+ if(parent::___processInputFile($input, $pagefile, $n)) $changed = true;
+
+ return $changed;
+ }
+
/**
* Process an action on a Pagefile/Pageimage
*
diff --git a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.scss b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.scss
index 3876bdc1..e7ac2de8 100755
--- a/wire/modules/Inputfield/InputfieldImage/InputfieldImage.scss
+++ b/wire/modules/Inputfield/InputfieldImage/InputfieldImage.scss
@@ -102,7 +102,7 @@ $itemPadding: 0.4em;
position: absolute;
top: 50%;
left: 50%;
- transition: transform ease .3s;
+ //transition: transform ease .3s;
-ms-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
}
@@ -466,19 +466,32 @@ $itemPadding: 0.4em;
& > div {
// Fix for Firefox not respecting aspect ratio on images
width: 100%;
+ position: relative;
}
-
.detail {
- display: block;
text-align: center;
- opacity: 0;
margin-top: 2px;
}
- &:hover .detail {
+ .detail-upload {
+ display: block;
+ opacity: 0;
+ }
+ .detail-focus {
+ display: none;
+ }
+ &:hover .detail-upload {
opacity: 0.7;
}
+ & > div.focusWrap .detail-upload {
+ display: none;
+ }
+ & > div.focusWrap .detail-focus {
+ display: block;
+ opacity: 0.7;
+ }
+
}
-
+
&__replace img {
// dragging new img on top of old one
opacity: 0.5;
@@ -546,6 +559,33 @@ $itemPadding: 0.4em;
}
+.gridImage__overflow .focusArea,
+.InputfieldImageEdit .focusArea {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ color: #fff;
+ cursor: default;
+
+ &.focusActive {
+ display: block;
+ overflow: hidden;
+ }
+
+ .focusCircle {
+ cursor: move;
+ width: 40px;
+ height: 40px;
+ border: 3px solid #fff;
+ border-radius: 50%;
+ background-color: rgba(255,255,255,0.3);
+ }
+}
+
+
.InputfieldImage .ImageData {
// element in a .gridImage item that contains the details and editable content
display: none;
@@ -670,6 +710,8 @@ $itemPadding: 0.4em;
& > img {
display: block;
position: static !important;
+ top: 0;
+ left: 0;
transition: none;
-ms-transform: none;
transform: none;
@@ -678,6 +720,12 @@ $itemPadding: 0.4em;
height: initial !important;
cursor: move;
}
+
+ & > .focusArea {
+ position: absolute;
+ top: 10px; // to match padding of __overflow
+ left: 10px;
+ }
}
.ImageData {