diff --git a/MediaflowPlugin.php b/MediaflowPlugin.php
index 6cf1b45..e70233a 100644
--- a/MediaflowPlugin.php
+++ b/MediaflowPlugin.php
@@ -6,6 +6,40 @@
class MediaflowPlugin extends BasePlugin {
+ public function init()
+ {
+ craft()->on('entries.onBeforeSaveEntry', function($event)
+ {
+ $entry = $event->params['entry'];
+ $fieldLayout = $entry->getFieldLayout();
+ $fields = $fieldLayout->getFields();
+
+ foreach ($fields as $fieldLayoutField) {
+ $field = $fieldLayoutField->getField();
+ $isMediaField = $field->getFieldType() instanceof Mediaflow_MediaFieldType;
+ if (!$isMediaField) {
+ continue;
+ }
+
+ $handle = $field->handle;
+ $fieldValue = $entry->$handle;
+ $supportsCrop = is_array($fieldValue->version) || $fieldValue->version instanceof \Traversable;
+ if ($supportsCrop) {
+ $urls = $fieldValue->urls ?: array();
+ foreach ($fieldValue->version as $name => $version) {
+ $hash = null;
+ if (isset($urls[$name]) && isset($urls[$name]['hash'])) {
+ $hash = $urls[$name]['hash'];
+ }
+ $urls[$name] = $fieldValue->saveVersion($name, $entry->slug, $hash);
+ }
+ $fieldValue->urls = $urls;
+ $entry->getContent()->setAttribute($handle, $fieldValue);
+ }
+ }
+ });
+ }
+
public function getName()
{
return Craft::t('Mediaflow');
@@ -13,12 +47,12 @@ public function getName()
public function getVersion()
{
- return '0.1.2';
+ return '1.0.0-rc1';
}
public function getDeveloper()
{
- return 'KeyTeq Labs';
+ return 'Keyteq Labs';
}
public function getDeveloperUrl()
@@ -29,10 +63,11 @@ public function getDeveloperUrl()
protected function defineSettings()
{
+ $string = AttributeType::String;
return array(
- 'url' => array(AttributeType::String, 'required' => true, 'label' => 'URL', 'default' => Craft::t('Mediaflow URL')),
- 'username' => array(AttributeType::String, 'required' => true, 'label' => 'Username', 'default' => Craft::t('Username')),
- 'apiKey' => array(AttributeType::String, 'required' => true, 'label' => 'API Key', 'default' => Craft::t('API key'))
+ 'url' => array($string, 'required' => true, 'label' => 'URL', 'default' => Craft::t('Mediaflow URL')),
+ 'username' => array($string, 'required' => true, 'label' => 'Username', 'default' => Craft::t('Username')),
+ 'apiKey' => array($string, 'required' => true, 'label' => 'API Key', 'default' => Craft::t('API key'))
);
}
diff --git a/README.md b/README.md
index cfd8605..c832261 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,9 @@ Craft CMS Keyteq Mediaflow plugin. Read more [Visit Mediaflow!](http://getmediaf
```
-### Using Picturefill.js
-```
+If you use Interchange and have set crops for your image you can
+have Mediaflow build the interchange string straightup for you by specifying the crop aliases:
+
+```smarty
+
```
diff --git a/composer.json b/composer.json
index 58be5f7..8c6cef1 100644
--- a/composer.json
+++ b/composer.json
@@ -16,7 +16,7 @@
],
"require": {
"php": ">=5.3.0",
- "keyteqlabs/keymedia": "~1.1",
+ "keyteqlabs/keymedia": "~1.2",
"composer/installers": "~1.0"
},
"autoload": {
diff --git a/composer.lock b/composer.lock
index 918cc14..67903fb 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,20 +4,20 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "6b09e52cdcf30f80f42d115da51479db",
+ "hash": "da7dec7c2626742f7c0c782e7865478b",
"packages": [
{
"name": "composer/installers",
- "version": "v1.0.19",
+ "version": "v1.0.20",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
- "reference": "89d77bfbee79e16653f7162c86e602cc188471db"
+ "reference": "1bff8aa77a18f3616f468ed8000cf86a5725bac3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/installers/zipball/89d77bfbee79e16653f7162c86e602cc188471db",
- "reference": "89d77bfbee79e16653f7162c86e602cc188471db",
+ "url": "https://api.github.com/repos/composer/installers/zipball/1bff8aa77a18f3616f468ed8000cf86a5725bac3",
+ "reference": "1bff8aa77a18f3616f468ed8000cf86a5725bac3",
"shasum": ""
},
"replace": {
@@ -59,6 +59,7 @@
"Hurad",
"MODX Evo",
"OXID",
+ "SMF",
"Thelia",
"WolfCMS",
"agl",
@@ -97,20 +98,20 @@
"zend",
"zikula"
],
- "time": "2014-11-29 01:29:17"
+ "time": "2015-01-11 03:51:11"
},
{
"name": "keyteqlabs/keymedia",
- "version": "v1.1.1",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/KeyteqLabs/keymedia-php.git",
- "reference": "cbb39681dde79ca0e11df29bf16e5b0353a74681"
+ "reference": "a1f03533d6ec737b9089a67ec97b2544bf741b84"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/KeyteqLabs/keymedia-php/zipball/cbb39681dde79ca0e11df29bf16e5b0353a74681",
- "reference": "cbb39681dde79ca0e11df29bf16e5b0353a74681",
+ "url": "https://api.github.com/repos/KeyteqLabs/keymedia-php/zipball/a1f03533d6ec737b9089a67ec97b2544bf741b84",
+ "reference": "a1f03533d6ec737b9089a67ec97b2544bf741b84",
"shasum": ""
},
"require": {
@@ -148,7 +149,7 @@
}
],
"description": "Keymedia API wrapper in PHP",
- "time": "2014-12-17 12:32:14"
+ "time": "2015-01-23 13:18:32"
},
{
"name": "mashape/unirest-php",
diff --git a/fieldtypes/Mediaflow_MediaFieldType.php b/fieldtypes/Mediaflow_MediaFieldType.php
index c5175b3..7288732 100644
--- a/fieldtypes/Mediaflow_MediaFieldType.php
+++ b/fieldtypes/Mediaflow_MediaFieldType.php
@@ -8,6 +8,11 @@ public function getName()
return Craft::t('Mediaflow item');
}
+ public function getSearchKeywords($value)
+ {
+ return 'mediaflow';
+ }
+
public function getInputHtml($name, $value)
{
$id = craft()->templates->namespaceInputId($name);
@@ -17,16 +22,24 @@ public function getInputHtml($name, $value)
return craft()->templates->render('mediaflow/input', array(
'id' => $id,
'name' => $name,
+ 'settings' => $this->getSettings(),
'value' => $value ? $value->getAttributes() : $emptyDefaults,
'emptyDefaults' => $emptyDefaults
));
}
+ public function getStaticHtml($value)
+ {
+ $inputHtml = $this->getInputHtml($this->model->attributes['handle'], $value);
+ $inputHtml = preg_replace('/<(?:input|textarea|select|button)\s[^>]*/i', '$0 disabled', $inputHtml);
+
+ return $inputHtml;
+ }
+
public function prepValue($value) {
if (!$value) {
return null;
}
- $data = array();
$copy = array(
'name' => 'name',
'host' => 'host',
@@ -36,13 +49,19 @@ public function prepValue($value) {
'thumbnailUrl' => 'thumb',
'thumb' => 'thumb',
'_id' => 'id',
- 'id' => 'id'
+ 'id' => 'id',
+ 'version' => 'version',
+ 'versions' => 'versions',
+ 'urls' => 'urls'
);
+ $data = array();
foreach ($copy as $now => $key) {
if (isset($value[$now])) {
$data[$key] = $value[$now];
}
}
+ $data['fieldtype-settings'] = $this->getSettings();
+ $data['versions'] = $data['fieldtype-settings']['versions'];
if (isset($value['file'])) {
$file = $value['file'];
$data['file'] = array(
@@ -56,6 +75,13 @@ public function prepValue($value) {
);
}
$model = Mediaflow_MediaModel::populateModel($data);
+ if (!isset($data['shareUrl'])) {
+ $model->shareUrl = $model->url(array(
+ 'width' => 2000,
+ 'height' => 2000,
+ 'crop' => false
+ ));
+ }
return $model;
}
@@ -70,6 +96,31 @@ public function prepValueFromPost($value)
return $value;
}
+ public function prepSettings($settings)
+ {
+ return array(
+ 'versions' => json_decode($settings['versions']) ?: array()
+ );
+ }
+
+ public function defineSettings()
+ {
+ return array('versions' => AttributeType::Mixed);
+ }
+
+ /**
+ * @inheritDoc ISavableComponentType::getSettingsHtml()
+ *
+ * @return string|null
+ */
+ public function getSettingsHtml()
+ {
+ // If they are both selected or nothing is selected, the select showBoth.
+ return craft()->templates->render('mediaflow/fieldtype-settings', array(
+ 'settings' => $this->getSettings()
+ ));
+ }
+
public function defineContentAttribute()
{
return AttributeType::Mixed;
diff --git a/models/Mediaflow_MediaModel.php b/models/Mediaflow_MediaModel.php
index 1a4b401..fea806d 100644
--- a/models/Mediaflow_MediaModel.php
+++ b/models/Mediaflow_MediaModel.php
@@ -1,5 +1,6 @@
AttributeType::Number,
'shareUrl' => AttributeType::String,
'file' => AttributeType::Mixed,
+ 'version' => AttributeType::Mixed,
+ 'versions' => AttributeType::Mixed,
+ 'urls' => AttributeType::Mixed,
));
}
- public function url(array $options = array()) {
+ public function saveVersion($name, $slug, $checksum)
+ {
+ if (!isset($this->version[$name])) {
+ return null;
+ }
+ $data = $this->version[$name];
+ if (!is_array($data)) {
+ return null;
+ }
+ if (!isset($data['coords'])) {
+ $data = array('coords' => $data, 'width'=>100,'height'=>100);
+ }
+ list($x, $y, $w, $h) = $data['coords'];
+ list($basename) = explode('.', $this->name);
+ $hash = $this->getHash(array($x, $x+$w, $y, $y+$h));
+ if ($hash === $checksum) {
+ return null;
+ }
+ $payload = array(
+ 'slug' => $slug . '-' . $basename . '-' . $name,
+ 'width' => $data['width'],
+ 'height' => $data['height'],
+ 'coords' => array($x, $y, $x + $w, $y + $h)
+ );
+ $result = $this->_client()->addMediaVersion($this->id, $payload);
+ $response = $result['version'];
+ $response['hash'] = $hash;
+ return $response;
+ }
+
+ public function getHash($coords)
+ {
+ return md5(implode('-', $coords));
+ }
+
+ /**
+ * Example usage:
+ *
+ * This only works if you have specified crops with these names, and those crops have been set
+ */
+ public function interchange($versions = array())
+ {
+ $output = array();
+ foreach ($versions as $name) {
+ $output[] = '[' . $this->versionUrl($name) . " ($name)]";
+ }
+ return implode(', ', $output);
+ }
+
+ public function versionUrl($name)
+ {
+ if (isset($this->urls[$name])) {
+ $data = $this->urls[$name]; $host = craft()->plugins->getPlugin('mediaflow')->getSettings()->url;
+ $path = $data['media'] . '/' . $data['slug'];
+ return $host . $path . $this->file['ending'];
+ }
+ else {
+ $version = $this->versions[$name];
+ list($width, $height) = $version;
+ return $this->url(compact('width', 'height'));
+ }
+ }
+
+ public function url($options = array())
+ {
+ if (is_string($options)) {
+ return $this->versionUrl($options);
+ }
$options += array(
'width' => false,
'height' => false,
@@ -52,4 +123,9 @@ public function url(array $options = array()) {
}
return $url;
}
+
+ protected function _client() {
+ $s = craft()->plugins->getPlugin('mediaflow')->getSettings();
+ return new KeymediaClient($s->username, $s->url, $s->apiKey);
+ }
}
diff --git a/package.json b/package.json
index 960e623..1eecfb2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mediaflow-craft",
- "version": "0.0.6",
+ "version": "1.0.0-rc1",
"description": "Mediaflow plugin for Craft",
"main": "index.js",
"scripts": {
diff --git a/resources/_mixin.scss b/resources/_mixin.scss
index c327070..8e50f42 100644
--- a/resources/_mixin.scss
+++ b/resources/_mixin.scss
@@ -33,7 +33,6 @@
$color: #666;
background-color: #eee;
background-image: linear-gradient(45deg, $color 25%, transparent 25%, transparent 75%, $color 75%, $color),
- linear-gradient(45deg, $color 25%, transparent 25%, transparent 75%, $color 75%, $color);
- background-size: 8px;
- background-position:0 0, 4px 4px;
+ linear-gradient(135deg, $color 25%, transparent 25%, transparent 75%, $color 75%, $color);
+ background-size: 16px 16px;
}
diff --git a/resources/jcrop.css b/resources/jcrop.css
new file mode 100644
index 0000000..5f7f639
--- /dev/null
+++ b/resources/jcrop.css
@@ -0,0 +1,352 @@
+/*! Jcrop.css v2.0.0-RC1 - build: 20130909
+ * Copyright 2008-2013 Tapmodo Interactive LLC
+ * Free software under MIT License
+ **/
+
+/*
+ The outer-most container in a typical Jcrop instance
+ If you are having difficulty with formatting related to styles
+ on a parent element, place any fixes here or in a like selector
+
+ You can also style this element if you want to add a border, etc
+ A better method for styling can be seen below with .jcrop-light
+ (Add a class to the holder and style elements for that extended class)
+*/
+.jcrop-active {
+ direction: ltr;
+ text-align: left;
+ /* IE10 touch compatibility */
+ -ms-touch-action: none;
+}
+/* Selection Borders */
+.jcrop-border {
+ background: #ffffff url("Jcrop.gif");
+ line-height: 1px !important;
+ font-size: 0 !important;
+ overflow: hidden;
+ position: absolute;
+ filter: alpha(opacity=50) !important;
+ opacity: 0.5 !important;
+}
+.jcrop-border.ord-w,
+.jcrop-border.ord-e,
+.jcrop-border.ord-n {
+ top: 0px;
+}
+.jcrop-border.ord-n,
+.jcrop-border.ord-s {
+ left: 0px !important;
+ width: 100%;
+ height: 1px !important;
+}
+.jcrop-border.ord-w,
+.jcrop-border.ord-e {
+ height: 100%;
+ width: 1px !important;
+}
+.jcrop-border.ord-e {
+ right: 0;
+}
+.jcrop-border.ord-s {
+ bottom: 0;
+}
+.jcrop-selection {
+ position: absolute;
+}
+.jcrop-box {
+ display: block;
+ background: none;
+ border: none;
+ padding: 0;
+ font-size: 0;
+ z-index: 15;
+}
+.jcrop-box:focus {
+ outline: 1px rgba(128, 128, 128, 0.65) dotted;
+}
+.jcrop-active,
+.jcrop-box {
+ position: relative;
+}
+.jcrop-box {
+ z-index: 2;
+ width: 100%;
+ height: 100%;
+ cursor: move;
+}
+/* Selection Handles */
+.jcrop-handle {
+ z-index: 10;
+ background-color: rgba(49, 28, 28, 0.58);
+ border: 1px #eeeeee solid;
+ width: 8px;
+ height: 8px;
+ font-size: 0;
+ position: absolute;
+ filter: alpha(opacity=80) !important;
+ opacity: 0.8 !important;
+}
+.jcrop-handle.ord-n {
+ left: 50%;
+ margin-left: -5px;
+ margin-top: -5px;
+ top: 0;
+ cursor: n-resize;
+}
+.jcrop-handle.ord-s {
+ bottom: 0;
+ left: 50%;
+ margin-bottom: -5px;
+ margin-left: -5px;
+ cursor: s-resize;
+}
+.jcrop-handle.ord-e {
+ margin-right: -5px;
+ margin-top: -5px;
+ right: 0;
+ top: 50%;
+ cursor: e-resize;
+}
+.jcrop-handle.ord-w {
+ left: 0;
+ margin-left: -5px;
+ margin-top: -5px;
+ top: 50%;
+ cursor: w-resize;
+}
+.jcrop-handle.ord-nw {
+ left: 0;
+ margin-left: -5px;
+ margin-top: -5px;
+ top: 0;
+ cursor: nw-resize;
+}
+.jcrop-handle.ord-ne {
+ margin-right: -5px;
+ margin-top: -5px;
+ right: 0;
+ top: 0;
+ cursor: ne-resize;
+}
+.jcrop-handle.ord-se {
+ bottom: 0;
+ margin-bottom: -5px;
+ margin-right: -5px;
+ right: 0;
+ cursor: se-resize;
+}
+.jcrop-handle.ord-sw {
+ bottom: 0;
+ left: 0;
+ margin-bottom: -5px;
+ margin-left: -5px;
+ cursor: sw-resize;
+}
+/* Larger Selection Handles for Touch */
+.jcrop-touch .jcrop-handle {
+ z-index: 10;
+ background-color: rgba(49, 28, 28, 0.58);
+ border: 1px #eeeeee solid;
+ width: 16px;
+ height: 16px;
+ font-size: 0;
+ position: absolute;
+ filter: alpha(opacity=80) !important;
+ opacity: 0.8 !important;
+}
+.jcrop-touch .jcrop-handle.ord-n {
+ left: 50%;
+ margin-left: -9px;
+ margin-top: -9px;
+ top: 0;
+ cursor: n-resize;
+}
+.jcrop-touch .jcrop-handle.ord-s {
+ bottom: 0;
+ left: 50%;
+ margin-bottom: -9px;
+ margin-left: -9px;
+ cursor: s-resize;
+}
+.jcrop-touch .jcrop-handle.ord-e {
+ margin-right: -9px;
+ margin-top: -9px;
+ right: 0;
+ top: 50%;
+ cursor: e-resize;
+}
+.jcrop-touch .jcrop-handle.ord-w {
+ left: 0;
+ margin-left: -9px;
+ margin-top: -9px;
+ top: 50%;
+ cursor: w-resize;
+}
+.jcrop-touch .jcrop-handle.ord-nw {
+ left: 0;
+ margin-left: -9px;
+ margin-top: -9px;
+ top: 0;
+ cursor: nw-resize;
+}
+.jcrop-touch .jcrop-handle.ord-ne {
+ margin-right: -9px;
+ margin-top: -9px;
+ right: 0;
+ top: 0;
+ cursor: ne-resize;
+}
+.jcrop-touch .jcrop-handle.ord-se {
+ bottom: 0;
+ margin-bottom: -9px;
+ margin-right: -9px;
+ right: 0;
+ cursor: se-resize;
+}
+.jcrop-touch .jcrop-handle.ord-sw {
+ bottom: 0;
+ left: 0;
+ margin-bottom: -9px;
+ margin-left: -9px;
+ cursor: sw-resize;
+}
+/* Selection Dragbars */
+.jcrop-dragbar {
+ font-size: 0;
+ z-index: 8;
+ position: absolute;
+}
+.jcrop-dragbar.ord-n,
+.jcrop-dragbar.ord-s {
+ height: 8px !important;
+ width: 100%;
+}
+.jcrop-dragbar.ord-e,
+.jcrop-dragbar.ord-w {
+ top: 0px;
+ height: 100%;
+ width: 8px !important;
+}
+.jcrop-dragbar.ord-n {
+ margin-top: -5px;
+ cursor: n-resize;
+ top: 0px;
+}
+.jcrop-dragbar.ord-s {
+ bottom: 0;
+ margin-bottom: -5px;
+ cursor: s-resize;
+}
+.jcrop-dragbar.ord-e {
+ margin-right: -5px;
+ right: 0;
+ cursor: e-resize;
+}
+.jcrop-dragbar.ord-w {
+ margin-left: -5px;
+ cursor: w-resize;
+}
+/* Shading panels */
+.jcrop-shades {
+ position: relative;
+ top: 0;
+ left: 0;
+ z-index: 10;
+}
+.jcrop-shades div {
+ cursor: crosshair;
+}
+/* Various special states */
+.jcrop-noresize .jcrop-dragbar,
+.jcrop-noresize .jcrop-handle {
+ display: none;
+}
+.jcrop-selection.jcrop-nodrag .jcrop-box,
+.jcrop-nodrag .jcrop-shades div {
+ cursor: default;
+}
+/* The "jcrop-light" class/extension */
+.jcrop-light .jcrop-border {
+ background: #ffffff;
+ filter: alpha(opacity=70) !important;
+ opacity: .70!important;
+}
+.jcrop-light .jcrop-handle {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ background-color: #000000;
+ border-color: #ffffff;
+ border-radius: 3px;
+}
+/* The "jcrop-dark" class/extension */
+.jcrop-dark .jcrop-border {
+ background: #000000;
+ filter: alpha(opacity=70) !important;
+ opacity: 0.7 !important;
+}
+.jcrop-dark .jcrop-handle {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ background-color: #ffffff;
+ border-color: #000000;
+ border-radius: 3px;
+}
+/* Simple macro to turn off the antlines */
+.solid-line .jcrop-border {
+ background: #ffffff;
+}
+.jcrop-thumb {
+ position: absolute;
+ overflow: hidden;
+ z-index: 35;
+}
+/* Fix for twitter bootstrap et al. */
+.jcrop-active img,
+.jcrop-thumb img,
+.jcrop-thumb canvas {
+ min-width: none;
+ min-height: none;
+ max-width: none;
+ max-height: none;
+}
+/* Improved multiple selection styles - in progress */
+.jcrop-hl-active .jcrop-border {
+ filter: alpha(opacity=20) !important;
+ opacity: .20!important;
+}
+.jcrop-hl-active .jcrop-handle {
+ filter: alpha(opacity=10) !important;
+ opacity: .10!important;
+}
+.jcrop-hl-active .jcrop-selection:hover {
+ /*
+ .jcrop-handle {
+ filter:Alpha(opacity=35)!important;
+ opacity:.35!important;
+ }
+ */
+
+}
+.jcrop-hl-active .jcrop-selection:hover .jcrop-border {
+ background-color: #ccc;
+ filter: alpha(opacity=50) !important;
+ opacity: .50!important;
+}
+.jcrop-hl-active .jcrop-selection.jcrop-current .jcrop-border {
+ background: #808080 url('Jcrop.gif');
+ opacity: .35!important;
+ filter: alpha(opacity=35) !important;
+}
+.jcrop-hl-active .jcrop-selection.jcrop-current .jcrop-handle {
+ filter: alpha(opacity=30) !important;
+ opacity: .30!important;
+}
+.jcrop-hl-active .jcrop-selection.jcrop-focus .jcrop-border {
+ background: url('Jcrop.gif');
+ opacity: .65!important;
+ filter: alpha(opacity=65) !important;
+}
+.jcrop-hl-active .jcrop-selection.jcrop-focus .jcrop-handle {
+ filter: alpha(opacity=60) !important;
+ opacity: .60!important;
+}
diff --git a/resources/jcrop.js b/resources/jcrop.js
new file mode 100644
index 0000000..dce8d4f
--- /dev/null
+++ b/resources/jcrop.js
@@ -0,0 +1,1929 @@
+/*! Jcrop.js v2.0.0-RC1 - build: 20130914
+ * @copyright 2008-2013 Tapmodo Interactive LLC
+ * @license Free software under MIT License
+ * @website http://jcrop.org/
+ **/
+(function($){
+ 'use strict';
+
+ /**
+ * BackoffFilter
+ * move out-of-bounds selection into allowed position at same size
+ */
+ var BackoffFilter = function(){
+ this.minw = 40;
+ this.minh = 40;
+ this.maxw = 0;
+ this.maxh = 0;
+ this.core = null;
+ };
+ $.extend(BackoffFilter.prototype,{
+ tag: 'backoff',
+ priority: 22,
+ filter: function(b){
+ var r = this.bound;
+
+ if (b.x < r.minx) { b.x = r.minx; b.x2 = b.w + b.x; }
+ if (b.y < r.miny) { b.y = r.miny; b.y2 = b.h + b.y; }
+ if (b.x2 > r.maxx) { b.x2 = r.maxx; b.x = b.x2 - b.w; }
+ if (b.y2 > r.maxy) { b.y2 = r.maxy; b.y = b.y2 - b.h; }
+
+ return b;
+ },
+ refresh: function(sel){
+ this.elw = sel.core.container.width();
+ this.elh = sel.core.container.height();
+ this.bound = {
+ minx: 0 + sel.edge.w,
+ miny: 0 + sel.edge.n,
+ maxx: this.elw + sel.edge.e,
+ maxy: this.elh + sel.edge.s
+ };
+ }
+ });
+
+ /**
+ * ConstrainFilter
+ * a filter to constrain crop selection to bounding element
+ */
+ var ConstrainFilter = function(){
+ this.core = null;
+ };
+ $.extend(ConstrainFilter.prototype,{
+ tag: 'constrain',
+ priority: 5,
+ filter: function(b,ord){
+ if (ord == 'move') {
+ if (b.x < this.minx) { b.x = this.minx; b.x2 = b.w + b.x; }
+ if (b.y < this.miny) { b.y = this.miny; b.y2 = b.h + b.y; }
+ if (b.x2 > this.maxx) { b.x2 = this.maxx; b.x = b.x2 - b.w; }
+ if (b.y2 > this.maxy) { b.y2 = this.maxy; b.y = b.y2 - b.h; }
+ } else {
+ if (b.x < this.minx) { b.x = this.minx; }
+ if (b.y < this.miny) { b.y = this.miny; }
+ if (b.x2 > this.maxx) { b.x2 = this.maxx; }
+ if (b.y2 > this.maxy) { b.y2 = this.maxy; }
+ }
+ b.w = b.x2 - b.x;
+ b.h = b.y2 - b.y;
+ return b;
+ },
+ refresh: function(sel){
+ this.elw = sel.core.container.width();
+ this.elh = sel.core.container.height();
+ this.minx = 0 + sel.edge.w;
+ this.miny = 0 + sel.edge.n;
+ this.maxx = this.elw + sel.edge.e;
+ this.maxy = this.elh + sel.edge.s;
+ }
+ });
+
+ /**
+ * ExtentFilter
+ * a filter to implement minimum or maximum size
+ */
+ var ExtentFilter = function(){
+ this.core = null;
+ };
+ $.extend(ExtentFilter.prototype,{
+ tag: 'extent',
+ priority: 12,
+ offsetFromCorner: function(corner,box,b){
+ var w = box[0], h = box[1];
+ switch(corner){
+ case 'bl': return [ b.x2 - w, b.y, w, h ];
+ case 'tl': return [ b.x2 - w , b.y2 - h, w, h ];
+ case 'br': return [ b.x, b.y, w, h ];
+ case 'tr': return [ b.x, b.y2 - h, w, h ];
+ }
+ },
+ getQuadrant: function(s){
+ var relx = s.opposite[0]-s.offsetx
+ var rely = s.opposite[1]-s.offsety;
+
+ if ((relx < 0) && (rely < 0)) return 'br';
+ else if ((relx >= 0) && (rely >= 0)) return 'tl';
+ else if ((relx < 0) && (rely >= 0)) return 'tr';
+ return 'bl';
+ },
+ filter: function(b,ord,sel){
+
+ if (ord == 'move') return b;
+
+ var w = b.w, h = b.h, st = sel.state, r = this.limits;
+ var quad = st? this.getQuadrant(st): 'br';
+
+ if (r.minw && (w < r.minw)) w = r.minw;
+ if (r.minh && (h < r.minh)) h = r.minh;
+ if (r.maxw && (w > r.maxw)) w = r.maxw;
+ if (r.maxh && (h > r.maxh)) h = r.maxh;
+
+ if ((w == b.w) && (h == b.h)) return b;
+
+ return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,[w,h],b));
+ },
+ refresh: function(sel){
+ this.elw = sel.core.container.width();
+ this.elh = sel.core.container.height();
+
+ this.limits = {
+ minw: sel.minSize[0],
+ minh: sel.minSize[1],
+ maxw: sel.maxSize[0],
+ maxh: sel.maxSize[1]
+ };
+ }
+ });
+
+ /**
+ * GridFilter
+ * a rudimentary grid effect
+ */
+ var GridFilter = function(){
+ this.stepx = 1;
+ this.stepy = 1;
+ this.core = null;
+ };
+ $.extend(GridFilter.prototype,{
+ tag: 'grid',
+ priority: 19,
+ filter: function(b){
+
+ var n = {
+ x: Math.round(b.x / this.stepx) * this.stepx,
+ y: Math.round(b.y / this.stepy) * this.stepy,
+ x2: Math.round(b.x2 / this.stepx) * this.stepx,
+ y2: Math.round(b.y2 / this.stepy) * this.stepy
+ };
+
+ n.w = n.x2 - n.x;
+ n.h = n.y2 - n.y;
+
+ return n;
+ }
+ });
+
+ /**
+ * RatioFilter
+ * implements aspectRatio locking
+ */
+ var RatioFilter = function(){
+ this.ratio = 0;
+ this.core = null;
+ };
+ $.extend(RatioFilter.prototype,{
+ tag: 'ratio',
+ priority: 15,
+ offsetFromCorner: function(corner,box,b){
+ var w = box[0], h = box[1];
+ switch(corner){
+ case 'bl': return [ b.x2 - w, b.y, w, h ];
+ case 'tl': return [ b.x2 - w , b.y2 - h, w, h ];
+ case 'br': return [ b.x, b.y, w, h ];
+ case 'tr': return [ b.x, b.y2 - h, w, h ];
+ }
+ },
+ getBoundRatio: function(b,quad){
+ var box = Jcrop.getLargestBox(this.ratio,b.w,b.h);
+ return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,box,b));
+ },
+ getQuadrant: function(s){
+ var relx = s.opposite[0]-s.offsetx
+ var rely = s.opposite[1]-s.offsety;
+
+ if ((relx < 0) && (rely < 0)) return 'br';
+ else if ((relx >= 0) && (rely >= 0)) return 'tl';
+ else if ((relx < 0) && (rely >= 0)) return 'tr';
+ return 'bl';
+ },
+ filter: function(b,ord,sel){
+
+ if (!this.ratio) return b;
+
+ var rt = b.w / b.h;
+ var st = sel.state;
+
+ var quad = st? this.getQuadrant(st): 'br';
+ ord = ord || 'se';
+
+ if (ord == 'move') return b;
+
+ switch(ord) {
+ case 'n':
+ b.x2 = this.elw;
+ b.w = b.x2 - b.x;
+ quad = 'tr';
+ break;
+ case 's':
+ b.x2 = this.elw;
+ b.w = b.x2 - b.x;
+ quad = 'br';
+ break;
+ case 'e':
+ b.y2 = this.elh;
+ b.h = b.y2 - b.y;
+ quad = 'br';
+ break;
+ case 'w':
+ b.y2 = this.elh;
+ b.h = b.y2 - b.y;
+ quad = 'bl';
+ break;
+ }
+
+ return this.getBoundRatio(b,quad);
+ },
+ refresh: function(sel){
+ this.ratio = sel.aspectRatio;
+ this.elw = sel.core.container.width();
+ this.elh = sel.core.container.height();
+ }
+ });
+
+ /**
+ * RoundFilter
+ * rounds coordinate values to integers
+ */
+ var RoundFilter = function(){
+ this.core = null;
+ };
+ $.extend(RoundFilter.prototype,{
+ tag: 'round',
+ priority: 90,
+ filter: function(b){
+
+ var n = {
+ x: Math.round(b.x),
+ y: Math.round(b.y),
+ x2: Math.round(b.x2),
+ y2: Math.round(b.y2)
+ };
+
+ n.w = n.x2 - n.x;
+ n.h = n.y2 - n.y;
+
+ return n;
+ }
+ });
+
+ /**
+ * ShadeFilter
+ * A filter that implements div-based shading on any element
+ *
+ * The shading you see is actually four semi-opaque divs
+ * positioned inside the container, around the selection
+ */
+ var ShadeFilter = function(opacity,color){
+ this.color = color || 'black';
+ this.opacity = opacity || 0.5;
+ this.core = null;
+ this.shades = {};
+ };
+ $.extend(ShadeFilter.prototype,{
+ tag: 'shader',
+ fade: true,
+ fadeEasing: 'swing',
+ fadeSpeed: 320,
+ priority: 95,
+ init: function(){
+ var t = this;
+
+ if (!t.attached) {
+ t.visible = false;
+
+ t.container = $('