From c80b2ee344445db21887e98df6869a3f6e80aa3f Mon Sep 17 00:00:00 2001 From: Raymond Julin Date: Fri, 23 Jan 2015 14:11:28 +0100 Subject: [PATCH 01/14] Wo-ha: Image cropping support Go to a specific thumb and type in some fancy-smancy JSON to define your scaled versions. Then edit an entry with a media selected and boom hit "Crop" to get a scaler view where you can select 1 crop for each scale version you set up. This adds some new semantics to templating mediaflow fields as well: ```twig {{entry.image.url('thumb')}} ``` Loads the `thumb` version from this field settings example: ```js {"thumb":[100,100]} ``` --- MediaflowPlugin.php | 32 +- composer.json | 2 +- composer.lock | 23 +- fieldtypes/Mediaflow_MediaFieldType.php | 41 +- models/Mediaflow_MediaModel.php | 57 +- resources/jcrop.css | 352 +++++ resources/jcrop.js | 1929 +++++++++++++++++++++++ resources/mediaflow-ng.js | 3 +- resources/ng-image-crop.html | 13 + resources/ng-image-crop.js | 83 + resources/style.css | 10 +- resources/style.scss | 15 +- templates/fieldtype-settings.html | 7 + templates/input.html | 41 +- 14 files changed, 2569 insertions(+), 39 deletions(-) create mode 100644 resources/jcrop.css create mode 100644 resources/jcrop.js create mode 100644 resources/ng-image-crop.html create mode 100644 resources/ng-image-crop.js create mode 100644 templates/fieldtype-settings.html diff --git a/MediaflowPlugin.php b/MediaflowPlugin.php index 6cf1b45..830963a 100644 --- a/MediaflowPlugin.php +++ b/MediaflowPlugin.php @@ -6,6 +6,27 @@ class MediaflowPlugin extends BasePlugin { + public function init() + { + craft()->on('entries.onBeforeSaveEntry', function($event) + { + $entry = $event->params['entry']; + if (isset($entry->image)) { + $image = $entry->image; + $urls = $image->urls ?: array(); + foreach ($image->version as $name => $version) { + $hash = null; + if (isset($urls[$name]) && isset($urls[$name]['hash'])) { + $hash = $urls[$name]['hash']; + } + $urls[$name] = $image->saveVersion($name, $entry->slug, $hash); + } + $image->urls = $urls; + $entry->getContent()->setAttributes(compact('image')); + } + }); + } + public function getName() { return Craft::t('Mediaflow'); @@ -13,12 +34,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 +50,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/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..2300cea 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,6 +22,7 @@ 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 )); @@ -26,7 +32,6 @@ public function prepValue($value) { if (!$value) { return null; } - $data = array(); $copy = array( 'name' => 'name', 'host' => 'host', @@ -36,13 +41,17 @@ public function prepValue($value) { 'thumbnailUrl' => 'thumb', 'thumb' => 'thumb', '_id' => 'id', - 'id' => 'id' + 'id' => 'id', + 'version' => 'version', + 'urls' => 'urls' ); + $data = array(); foreach ($copy as $now => $key) { if (isset($value[$now])) { $data[$key] = $value[$now]; } } + $data['fieldtype-settings'] = $this->getSettings(); if (isset($value['file'])) { $file = $value['file']; $data['file'] = array( @@ -55,8 +64,7 @@ public function prepValue($value) { 'ending' => isset($file['ending']) ? $file['ending'] : null ); } - $model = Mediaflow_MediaModel::populateModel($data); - return $model; + return Mediaflow_MediaModel::populateModel($data); } public function prepValueFromPost($value) @@ -70,6 +78,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..d916077 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, + '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)); + } + + public function versionUrl($name) + { + $data = $this->urls[$name]; + $host = craft()->plugins->getPlugin('mediaflow')->getSettings()->url; + $path = $data['media'] . '/' . $data['slug']; + return $host . $path . $this->file['ending']; + } + + public function url($options = array()) + { + if (is_string($options)) { + return $this->versionUrl($options); + } $options += array( 'width' => false, 'height' => false, @@ -52,4 +102,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/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 = $('
').addClass(t.core.opt.css_shades) + .prependTo(this.core.container).hide(); + + t.elh = this.core.container.height(); + t.elw = this.core.container.width(); + + t.shades = { + top: t.createShade(), + right: t.createShade(), + left: t.createShade(), + bottom: t.createShade() + }; + + t.attached = true; + } + }, + destroy: function(){ + this.container.remove(); + }, + setColor: function(color,instant){ + var t = this; + + if (color == t.color) return t; + + this.color = color; + var colorfade = Jcrop.supportsColorFade(); + $.each(t.shades,function(u,i){ + if (!t.fade || instant || !colorfade) i.css('backgroundColor',color); + else i.animate({backgroundColor:color},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing}); + }); + return t; + }, + setOpacity: function(opacity,instant){ + var t = this; + + if (opacity == t.opacity) return t; + + t.opacity = opacity; + $.each(t.shades,function(u,i){ + if (!t.fade || instant) i.css({opacity:opacity}); + else i.animate({opacity:opacity},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing}); + }); + return t; + }, + createShade: function(){ + return $('
').css({ + position: 'absolute', + backgroundColor: this.color, + opacity: this.opacity + }).appendTo(this.container); + }, + refresh: function(sel){ + var m = this.core, s = this.shades; + + this.setColor(sel.bgColor?sel.bgColor:this.core.opt.bgColor); + this.setOpacity(sel.bgOpacity?sel.bgOpacity:this.core.opt.bgOpacity); + + this.elh = m.container.height(); + this.elw = m.container.width(); + s.right.css('height',this.elh+'px'); + s.left.css('height',this.elh+'px'); + }, + filter: function(b,ord,sel){ + + if (!sel.active) return b; + + var t = this, + s = t.shades; + + s.top.css({ + left: Math.round(b.x)+'px', + width: Math.round(b.w)+'px', + height: Math.round(b.y)+'px' + }); + s.bottom.css({ + top: Math.round(b.y2)+'px', + left: Math.round(b.x)+'px', + width: Math.round(b.w)+'px', + height: (t.elh-Math.round(b.y2))+'px' + }); + s.right.css({ + left: Math.round(b.x2)+'px', + width: (t.elw-Math.round(b.x2))+'px' + }); + s.left.css({ + width: Math.round(b.x)+'px' + }); + + if (!t.visible) { + t.container.show(); + t.visible = true; + } + + return b; + } + }); + + /** + * CropAnimator + * manages smooth cropping animation + * + * This object is called internally to manage animation. + * An in-memory div is animated and a progress callback + * is used to update the selection coordinates of the + * visible selection in realtime. + */ + // var CropAnimator = function(selection){{{ + var CropAnimator = function(selection){ + this.selection = selection; + this.core = selection.core; + }; + // }}} + + $.extend(CropAnimator.prototype,{ + // getElement: function(){{{ + getElement: function(){ + var b = this.selection.get(); + + return $('
') + .css({ + position: 'absolute', + top: b.y+'px', + left: b.x+'px', + width: b.w+'px', + height: b.h+'px' + }); + }, + // }}} + // animate: function(x,y,w,h,cb){{{ + animate: function(x,y,w,h,cb){ + var t = this; + + t.selection.allowResize(false); + + t.getElement().animate({ + top: y+'px', + left: x+'px', + width: w+'px', + height: h+'px' + },{ + easing: t.core.opt.animEasing, + duration: t.core.opt.animDuration, + complete: function(){ + t.selection.allowResize(true); + cb && cb.call(this); + }, + progress: function(anim){ + var props = {}, i, tw = anim.tweens; + + for(i=0;i= 0) + return true; + + switch(e.keyCode){ + case 37: m.nudge(-nudge,0); break; + case 38: m.nudge(0,-nudge); break; + case 39: m.nudge(nudge,0); break; + case 40: m.nudge(0,nudge); break; + + case 46: + case 8: + m.requestDelete(); + return false; + break; + + default: + if (t.debug) console.log('keycode: ' + e.keyCode); + break; + } + + if (!e.metaKey && !e.ctrlKey) + e.preventDefault(); + }); + } + // }}} + } + }); + + /** + * Selection + * Built-in selection object + */ + var Selection = function(){}; + + $.extend(Selection,{ + // defaults: {{{ + defaults: { + minSize: [ 8, 8 ], + maxSize: [ 0, 0 ], + aspectRatio: 0, + edge: { n: 0, s: 0, e: 0, w: 0 }, + bgColor: null, + bgOpacity: null, + last: null, + + state: null, + active: true, + linked: true, + canDelete: true, + canDrag: true, + canResize: true, + canSelect: true + }, + // }}} + prototype: { + // init: function(core){{{ + init: function(core){ + this.core = core; + this.startup(); + this.linked = this.core.opt.linked; + this.attach(); + this.setOptions(this.core.opt); + core.container.trigger('cropcreate',[this]); + }, + // }}} + // attach: function(){{{ + attach: function(){ + // For extending init() sequence + }, + // }}} + // startup: function(){{{ + startup: function(){ + var t = this, o = t.core.opt; + $.extend(t,Selection.defaults); + t.filter = t.core.getDefaultFilters(); + + t.element = $('
').addClass(o.css_selection).data({ selection: t }); + t.frame = $(' +
+
+
+ +
diff --git a/resources/ng-image-crop.js b/resources/ng-image-crop.js new file mode 100644 index 0000000..14e962f --- /dev/null +++ b/resources/ng-image-crop.js @@ -0,0 +1,83 @@ +angular.module('mfImageCrop', []).directive('mfImageCrop', function() { + var objectify = function(data) { + var result = {}; + if (typeof data === 'string') { + try { result = JSON.parse(data); } + catch (e) {} + } + return result; + }; + return { + templateUrl: '/admin/resources/mediaflow/ng-image-crop.html', + scope: { + selected: '=' + }, + controller: function($scope) { + var ctrl = this; + ctrl.current = {name:null,size:[]} + ctrl.stopPropagation = function(e) { + e.preventDefault(); + e.stopPropagation(); + } + ctrl.saveCrop = function(e,s,c) { + ctrl.stopPropagation(e); + var selection = [c.x, c.y, c.w, c.h].map(Math.round); + console.log('set', ctrl.current, selection); + $scope.selected.version[ctrl.current.name] = { + coords: selection, + width: ctrl.current.size[0], + height: ctrl.current.size[1] + } + // Rawr + $scope.$apply(); + } + ctrl.defaultOptions = function(w,h) { + return { + aspectRatio: w/h, + minSize: [w,h] + } + }; + }, + link: function($scope, $element, attrs, ctrl) { + $scope.versions = objectify(attrs.versions); + if (!('version' in $scope.selected)) { + $scope.selected.version = {}; + } + + var onInitialize = function() { + this.container.on('cropstart cropmove', ctrl.stopPropagation); + this.container.on('cropend', ctrl.saveCrop); + // TODO PR to Jcrop 2.x to fucking fix this + this.container.find('button').attr('type', 'button'); + }; + + var $crop = $($element[0]).find('.cropper img'); + var setCrop = function(selection, size) { + var width = size[0], height = size[1]; + $crop.Jcrop({ + aspectRatio: width / height, + boxWidth: 400, + boxHeight: 600, + setSelect: (selection || [0, 0, width, height]) + }, onInitialize); + }; + + $scope.crop = function(name) { + var size = $scope.versions[name]; + var selection = null; + $scope.active = ctrl.current.name = name; + ctrl.current.size = size; + if (name in $scope.selected.version) { + selection = $scope.selected.version[name].coords; + } + console.log('load crop', name, selection); + setCrop(selection, size); + } + + for (var version in $scope.versions) { + $scope.crop(version); + break; + } + } + }; +}); diff --git a/resources/style.css b/resources/style.css index d90c298..5b626f4 100644 --- a/resources/style.css +++ b/resources/style.css @@ -71,9 +71,11 @@ transform: translate(-5px, -5px); } .mediaflow-fold .close { float: right; } + .mediaflow-fold.scaler:after { + left: 118px; } .mediaflow-fold:after { bottom: 100%; - left: 5%; + left: 56px; border: solid transparent; content: " "; height: 0; @@ -85,6 +87,12 @@ border-width: 10px; margin-left: -10px; } +.cropper-menu { + margin: 0 0 10px 0; + clear: both; } + .cropper-menu button { + text-transform: capitalize; } + @-webkit-keyframes mediaflow-spinner { 0% { -webkit-transform: rotate(0deg); diff --git a/resources/style.scss b/resources/style.scss index 009aafe..a1517ce 100644 --- a/resources/style.scss +++ b/resources/style.scss @@ -86,9 +86,13 @@ float: right; } + &.scaler:after { + left: 118px; + } + &:after { bottom: 100%; - left: 5%; + left: 56px; border: solid transparent; content: " "; height: 0; @@ -102,4 +106,13 @@ } } +.cropper-menu { + margin: 0 0 10px 0; + clear: both; + button { + text-transform: capitalize; + } +} + + @import 'spinner'; diff --git a/templates/fieldtype-settings.html b/templates/fieldtype-settings.html new file mode 100644 index 0000000..e958473 --- /dev/null +++ b/templates/fieldtype-settings.html @@ -0,0 +1,7 @@ +{% import "_includes/forms" as forms %} + +{{ forms.textareaField({ + id: 'versions', + name: 'versions', + value: settings.versions|json_encode() +}) }} diff --git a/templates/input.html b/templates/input.html index 94239a8..c0bc91e 100644 --- a/templates/input.html +++ b/templates/input.html @@ -34,17 +34,30 @@
- - + + + ng-click="selected = emptyDefaults;scaling = false">Remove +
+
+ + No media selected, no crop possible + +
+
+
-
{% includeJsResource "mediaflow/angular.min.js" %} {% includeJsResource "mediaflow/angularjs-file-upload.js" %} +{% includeJsResource "mediaflow/jcrop.js" %} +{% includeJsResource "mediaflow/ng-image-crop.js" %} {% includeJsResource "mediaflow/mediaflow-ng.js" %} + +{% includeCssResource "mediaflow/jcrop.css" %} {% includeCssResource "mediaflow/style.css" %} From 644c077029b347ce112803d84195ae0dd67c389b Mon Sep 17 00:00:00 2001 From: Raymond Julin Date: Fri, 23 Jan 2015 15:07:37 +0100 Subject: [PATCH 02/14] fix(cropping): Now works in Safari + Firefox --- package.json | 2 +- resources/ng-image-crop.html | 4 ++-- resources/ng-image-crop.js | 23 +++++++++++------------ templates/input.html | 3 +-- 4 files changed, 15 insertions(+), 17 deletions(-) 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/ng-image-crop.html b/resources/ng-image-crop.html index 0cd9291..7eae82c 100644 --- a/resources/ng-image-crop.html +++ b/resources/ng-image-crop.html @@ -2,8 +2,8 @@
diff --git a/resources/ng-image-crop.js b/resources/ng-image-crop.js index 14e962f..c9cc1e9 100644 --- a/resources/ng-image-crop.js +++ b/resources/ng-image-crop.js @@ -22,7 +22,6 @@ angular.module('mfImageCrop', []).directive('mfImageCrop', function() { ctrl.saveCrop = function(e,s,c) { ctrl.stopPropagation(e); var selection = [c.x, c.y, c.w, c.h].map(Math.round); - console.log('set', ctrl.current, selection); $scope.selected.version[ctrl.current.name] = { coords: selection, width: ctrl.current.size[0], @@ -56,28 +55,28 @@ angular.module('mfImageCrop', []).directive('mfImageCrop', function() { var width = size[0], height = size[1]; $crop.Jcrop({ aspectRatio: width / height, - boxWidth: 400, - boxHeight: 600, + boxWidth: 600, + boxHeight: 400, setSelect: (selection || [0, 0, width, height]) }, onInitialize); }; $scope.crop = function(name) { - var size = $scope.versions[name]; - var selection = null; + var size = ctrl.current.size = $scope.versions[name]; $scope.active = ctrl.current.name = name; - ctrl.current.size = size; + var selection = null; if (name in $scope.selected.version) { selection = $scope.selected.version[name].coords; } - console.log('load crop', name, selection); + setCrop(selection, size); setCrop(selection, size); } - - for (var version in $scope.versions) { - $scope.crop(version); - break; - } + setTimeout(function() { + for (var version in $scope.versions) { + $scope.crop(version); + break; + } + },100); } }; }); diff --git a/templates/input.html b/templates/input.html index c0bc91e..6f7d534 100644 --- a/templates/input.html +++ b/templates/input.html @@ -36,12 +36,11 @@ + ng-click="scaling = true; showMedia = false">Crop
- +
diff --git a/resources/ng-image-crop.js b/resources/ng-image-crop.js index 4e5f6f0..e54a12c 100644 --- a/resources/ng-image-crop.js +++ b/resources/ng-image-crop.js @@ -41,6 +41,8 @@ angular.module('mfImageCrop', []).directive('mfImageCrop', function() { }; }, link: function($scope, $element, attrs, ctrl) { + var boxWidth = 600; + var boxHeight = 400; $scope.versions = objectify(attrs.versions); if (!('version' in $scope.selected)) { $scope.selected.version = {}; @@ -58,8 +60,8 @@ angular.module('mfImageCrop', []).directive('mfImageCrop', function() { var width = size[0], height = size[1]; $crop.Jcrop({ aspectRatio: width / height, - boxWidth: 600, - boxHeight: 400, + boxWidth: boxWidth, + boxHeight: boxHeight, setSelect: (selection || [0, 0, width, height]) }, onInitialize); }; @@ -72,6 +74,19 @@ angular.module('mfImageCrop', []).directive('mfImageCrop', function() { setCrop(selection, size); setCrop(selection, size); } + if ($scope.selected) { + var w = $scope.selected.file.width; + var h = $scope.selected.file.height; + var ratio = w/h; + var boxRatio = boxWidth/boxHeight; + var factor = ratio > boxRatio ? w / boxWidth : h / boxHeight; + var iw = w / factor; + var ih = h / factor; + $scope.box = [iw,ih]; + } + else { + $scope.box = [100,100]; + } setTimeout(function() { for (var version in $scope.versions) { $scope.crop(version); From e33a9226518b75eaf818a04c576183dc36543185 Mon Sep 17 00:00:00 2001 From: Raymond Julin Date: Wed, 28 Jan 2015 10:46:50 +0100 Subject: [PATCH 05/14] fix(cropping): Checkered background on cropper image --- resources/_mixin.scss | 5 ++--- resources/style.css | 15 +++++++++------ resources/style.scss | 3 +++ 3 files changed, 14 insertions(+), 9 deletions(-) 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/style.css b/resources/style.css index 5b626f4..be3676d 100644 --- a/resources/style.css +++ b/resources/style.css @@ -11,9 +11,8 @@ .mediaflow-preview .thumb img { border-radius: 3px; background-color: #eee; - background-image: linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666), linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666); - background-size: 8px; - background-position: 0 0, 4px 4px; } + background-image: linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666), linear-gradient(135deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666); + background-size: 16px 16px; } .mediaflow-preview .mediaflow-meta th, .mediaflow-preview .mediaflow-meta td { padding: 0 10px 2px 0; } .mediaflow-preview .mediaflow-meta th, .mediaflow-preview .mediaflow-meta .title { @@ -53,9 +52,8 @@ height: 100px; border-radius: 3px; background-color: #eee; - background-image: linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666), linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666); - background-size: 8px; - background-position: 0 0, 4px 4px; + background-image: linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666), linear-gradient(135deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666); + background-size: 16px 16px; -webkit-transition: all 0.4s ease; -moz-transition: all 0.4s ease; -o-transition: all 0.4s ease; @@ -93,6 +91,11 @@ .cropper-menu button { text-transform: capitalize; } +.cropper .jcrop-active > img { + background-color: #eee; + background-image: linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666), linear-gradient(135deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666); + background-size: 16px 16px; } + @-webkit-keyframes mediaflow-spinner { 0% { -webkit-transform: rotate(0deg); diff --git a/resources/style.scss b/resources/style.scss index a1517ce..67a1319 100644 --- a/resources/style.scss +++ b/resources/style.scss @@ -113,6 +113,9 @@ text-transform: capitalize; } } +.cropper .jcrop-active>img { + @include checkered(); +} @import 'spinner'; From 96ebe454277bc217e2091dd812a2bbd769760f32 Mon Sep 17 00:00:00 2001 From: Raymond Julin Date: Wed, 28 Jan 2015 11:01:41 +0100 Subject: [PATCH 06/14] fix(cropping): Display checkered background on croppered image even before it is loaded --- resources/style.css | 2 +- resources/style.scss | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/style.css b/resources/style.css index be3676d..e251d9a 100644 --- a/resources/style.css +++ b/resources/style.css @@ -91,7 +91,7 @@ .cropper-menu button { text-transform: capitalize; } -.cropper .jcrop-active > img { +.cropper > img, .cropper .jcrop-active > img { background-color: #eee; background-image: linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666), linear-gradient(135deg, #666 25%, transparent 25%, transparent 75%, #666 75%, #666); background-size: 16px 16px; } diff --git a/resources/style.scss b/resources/style.scss index 67a1319..a804f41 100644 --- a/resources/style.scss +++ b/resources/style.scss @@ -113,8 +113,10 @@ text-transform: capitalize; } } -.cropper .jcrop-active>img { - @include checkered(); +.cropper { + &>img, .jcrop-active>img { + @include checkered(); + } } From 5c9e5ec93266ffd1e259df6ee0508911b33734ad Mon Sep 17 00:00:00 2001 From: Raymond Julin Date: Wed, 28 Jan 2015 11:07:42 +0100 Subject: [PATCH 07/14] fix(cropping): Support generating interchange syntax out of the box --- README.md | 7 +++++-- models/Mediaflow_MediaModel.php | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) 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/models/Mediaflow_MediaModel.php b/models/Mediaflow_MediaModel.php index d916077..39ef0c6 100644 --- a/models/Mediaflow_MediaModel.php +++ b/models/Mediaflow_MediaModel.php @@ -59,8 +59,25 @@ 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])) { + return null; + } $data = $this->urls[$name]; $host = craft()->plugins->getPlugin('mediaflow')->getSettings()->url; $path = $data['media'] . '/' . $data['slug']; From 7c0d3a5b8103de2ed21a63cd4c55766dd09e47ff Mon Sep 17 00:00:00 2001 From: Raymond Julin Date: Wed, 28 Jan 2015 12:52:26 +0100 Subject: [PATCH 08/14] fix(cropping): Fallback to focal point crops when an alias does not have a specific crop set --- fieldtypes/Mediaflow_MediaFieldType.php | 2 ++ models/Mediaflow_MediaModel.php | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/fieldtypes/Mediaflow_MediaFieldType.php b/fieldtypes/Mediaflow_MediaFieldType.php index cf5f078..b661af0 100644 --- a/fieldtypes/Mediaflow_MediaFieldType.php +++ b/fieldtypes/Mediaflow_MediaFieldType.php @@ -43,6 +43,7 @@ public function prepValue($value) { '_id' => 'id', 'id' => 'id', 'version' => 'version', + 'versions' => 'versions', 'urls' => 'urls' ); $data = array(); @@ -52,6 +53,7 @@ public function prepValue($value) { } } $data['fieldtype-settings'] = $this->getSettings(); + $data['versions'] = $data['fieldtype-settings']['versions']; if (isset($value['file'])) { $file = $value['file']; $data['file'] = array( diff --git a/models/Mediaflow_MediaModel.php b/models/Mediaflow_MediaModel.php index 39ef0c6..fea806d 100644 --- a/models/Mediaflow_MediaModel.php +++ b/models/Mediaflow_MediaModel.php @@ -20,6 +20,7 @@ protected function defineAttributes() 'shareUrl' => AttributeType::String, 'file' => AttributeType::Mixed, 'version' => AttributeType::Mixed, + 'versions' => AttributeType::Mixed, 'urls' => AttributeType::Mixed, )); } @@ -75,13 +76,16 @@ public function interchange($versions = array()) public function versionUrl($name) { - if (!isset($this->urls[$name])) { - return null; + 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')); } - $data = $this->urls[$name]; - $host = craft()->plugins->getPlugin('mediaflow')->getSettings()->url; - $path = $data['media'] . '/' . $data['slug']; - return $host . $path . $this->file['ending']; } public function url($options = array()) From 24d90593f76142a772c3814cd5177f095c285eba Mon Sep 17 00:00:00 2001 From: Raymond Julin Date: Thu, 29 Jan 2015 11:08:55 +0100 Subject: [PATCH 09/14] fix(cropping): Crop all mediaflow fields on entry --- MediaflowPlugin.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/MediaflowPlugin.php b/MediaflowPlugin.php index 2749f82..2ec87ee 100644 --- a/MediaflowPlugin.php +++ b/MediaflowPlugin.php @@ -11,18 +11,20 @@ public function init() craft()->on('entries.onBeforeSaveEntry', function($event) { $entry = $event->params['entry']; - if (isset($entry->image)) { - $image = $entry->image; - if (is_array($image->version) || $image->version instanceof \Traversable) { - $urls = $image->urls ?: array(); - foreach ($image->version as $name => $version) { + foreach ($entry as $field) { + $isMediaField = $field instanceof Mediaflow_MediaFieldType; + if (!$isMediaField) continue; + $supportsCrop = is_array($field->version) || $field->version instanceof \Traversable; + if ($supportsCrop) { + $urls = $field->urls ?: array(); + foreach ($field->version as $name => $version) { $hash = null; if (isset($urls[$name]) && isset($urls[$name]['hash'])) { $hash = $urls[$name]['hash']; } - $urls[$name] = $image->saveVersion($name, $entry->slug, $hash); + $urls[$name] = $field->saveVersion($name, $entry->slug, $hash); } - $image->urls = $urls; + $field->urls = $urls; $entry->getContent()->setAttributes(compact('image')); } } From 4776007d3cabe37e5a403a2f5416d8dd2e88fca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rnar=20Helland?= Date: Fri, 29 May 2015 13:05:01 +0200 Subject: [PATCH 10/14] Fixes upload and view in cp, and adds spinner to upload. Fixes #20, fixes #10 --- resources/mediaflow-ng.js | 51 ++++++++++++++++++++++++++------------- resources/style.css | 8 ++++++ resources/style.scss | 9 +++++++ templates/index.html | 45 +++++++++++++++++++--------------- 4 files changed, 76 insertions(+), 37 deletions(-) diff --git a/resources/mediaflow-ng.js b/resources/mediaflow-ng.js index 340f56e..55de73e 100644 --- a/resources/mediaflow-ng.js +++ b/resources/mediaflow-ng.js @@ -54,23 +54,6 @@ mediaflow.controller('MediaFlowCtrl', function ($scope, $http, $upload) { }); }); }; - - $scope.onFileSelect = function($files) { - $scope.spin = true; - for (var i = 0; i < $files.length; i++) { - var $file = $files[i]; - $scope.upload = $upload.upload({ - url: '/admin/mediaflow/upload', - file: $file - }).then(function(args) { - $scope.media.unshift(args.data); - $scope.spin = false; - }, function(args) { - console.log('err', args); - $scope.spin = false; - }); - } - } }); @@ -123,6 +106,23 @@ mediaflow.controller('MediaFlowFieldCtrl', function ($scope, $http, $upload) { if (!$el) { return; } $el.click(); }; + + $scope.onFileSelect = function($files) { + $scope.spin = true; + for (var i = 0; i < $files.length; i++) { + var $file = $files[i]; + $scope.upload = $upload.upload({ + url: '/admin/mediaflow/upload', + file: $file + }).then(function(args) { + $scope.media.unshift(args.data); + $scope.spin = false; + }, function(args) { + console.log('err', args); + $scope.spin = false; + }); + } + } }); mediaflow.controller('MediaFlowBrowseCtrl', function ($scope, $http, $upload) { @@ -148,4 +148,21 @@ mediaflow.controller('MediaFlowBrowseCtrl', function ($scope, $http, $upload) { timeout = setTimeout(updateMedia, 250, searchText); } }); + + $scope.onFileSelect = function($files) { + $scope.spin = true; + for (var i = 0; i < $files.length; i++) { + var $file = $files[i]; + $scope.upload = $upload.upload({ + url: '/admin/mediaflow/upload', + file: $file + }).then(function(args) { + $scope.media.unshift(args.data); + $scope.spin = false; + }, function(args) { + console.log('err', args); + $scope.spin = false; + }); + } + } }); diff --git a/resources/style.css b/resources/style.css index e251d9a..a7a784c 100644 --- a/resources/style.css +++ b/resources/style.css @@ -85,6 +85,14 @@ border-width: 10px; margin-left: -10px; } +.mediaflow-upload-cell { + width: 150px; } + .mediaflow-upload-cell .buttons .mediaflow-upload-button.btn { + margin-top: 3px; } + +.mediaflow-spinner { + left: 128px; } + .cropper-menu { margin: 0 0 10px 0; clear: both; } diff --git a/resources/style.scss b/resources/style.scss index a804f41..2d08d7c 100644 --- a/resources/style.scss +++ b/resources/style.scss @@ -105,6 +105,15 @@ margin-left: -10px; } } +.mediaflow-upload-cell { + width: 150px; + .buttons .mediaflow-upload-button.btn { + margin-top: 3px; + } +} +.mediaflow-spinner { + left:128px; +} .cropper-menu { margin: 0 0 10px 0; diff --git a/templates/index.html b/templates/index.html index f0a27af..d3d67af 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,25 +4,6 @@ {% set hasSidebar = true %} {% set sidebar %} -
- -
- - -
-