Added the Media tab, minor update in .htaccess to allow temporary public directory to be accessible; implemented the basic UI components and navigation; implemented grid, list and tiles view modes; implemented drag-select interface; implemented Media Library cache refreshing; implemented thumbnail generating for local and remote media files; fixed memory leak in third-party Flot Resize library; minor update in the AJAX framework - AJAX request cancelling is not considered as an error anymore; added back-end UI components for creating panels.
This commit is contained in:
parent
1c273f28ba
commit
18e058ad59
|
|
@ -21,6 +21,7 @@
|
|||
RewriteRule ^vendor/.* index.php [L,NC]
|
||||
RewriteRule ^storage/cms/.* index.php [L,NC]
|
||||
RewriteRule ^storage/logs/.* index.php [L,NC]
|
||||
RewriteCond %{REQUEST_URI} !storage/temp/public
|
||||
RewriteRule ^storage/temp/.* index.php [L,NC]
|
||||
RewriteRule ^storage/framework/.* index.php [L,NC]
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
"october/system": "~1.0",
|
||||
"october/backend": "~1.0",
|
||||
"october/cms": "~1.0",
|
||||
"october/rain": "~1.0"
|
||||
"october/rain": "~1.0",
|
||||
"league/flysystem-aws-s3-v2": "~1.0"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1833,10 +1833,10 @@ if(typeof item.series.data[item.dataIndex][1]==='number'){content=this.adjustVal
|
|||
if(typeof item.series.xaxis.tickFormatter!=='undefined'){content=content.replace(xPattern,item.series.xaxis.tickFormatter(item.series.data[item.dataIndex][0],item.series.xaxis));}
|
||||
if(typeof item.series.yaxis.tickFormatter!=='undefined'){content=content.replace(yPattern,item.series.yaxis.tickFormatter(item.series.data[item.dataIndex][1],item.series.yaxis));}
|
||||
return content;};FlotTooltip.prototype.isTimeMode=function(axisName,item){return(typeof item.series[axisName].options.mode!=='undefined'&&item.series[axisName].options.mode==='time');};FlotTooltip.prototype.isXDateFormat=function(item){return(typeof this.tooltipOptions.xDateFormat!=='undefined'&&this.tooltipOptions.xDateFormat!==null);};FlotTooltip.prototype.isYDateFormat=function(item){return(typeof this.tooltipOptions.yDateFormat!=='undefined'&&this.tooltipOptions.yDateFormat!==null);};FlotTooltip.prototype.timestampToDate=function(tmst,dateFormat){var theDate=new Date(tmst);return $.plot.formatDate(theDate,dateFormat);};FlotTooltip.prototype.adjustValPrecision=function(pattern,content,value){var precision;if(content.match(pattern)!==null){if(RegExp.$1!==''){precision=RegExp.$1;value=value.toFixed(precision);content=content.replace(pattern,value);}}
|
||||
return content;};var init=function(plot){new FlotTooltip(plot);};$.plot.plugins.push({init:init,options:defaultOptions,name:'tooltip',version:'0.6.1'});})(jQuery);(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)
|
||||
return content;};var init=function(plot){new FlotTooltip(plot);};$.plot.plugins.push({init:init,options:defaultOptions,name:'tooltip',version:'0.6.1'});})(jQuery);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)
|
||||
return;plot.resize();plot.setupGrid();plot.draw();}
|
||||
function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize);}
|
||||
function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize);}
|
||||
function bindEvents(plot,eventHolder){$(window).bind('resize',onResize)}
|
||||
function shutdown(plot,eventHolder){$(window).unbind('resize',onResize)}
|
||||
plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown);}
|
||||
$.plot.plugins.push({init:init,options:options,name:'resize',version:'1.0'});})(jQuery);(function($){var options={xaxis:{timezone:null,timeformat:null,twelveHourClock:false,monthNames:null}};function floorInBase(n,base){return base*Math.floor(n/base);}
|
||||
function formatDate(d,fmt,monthNames,dayNames){if(typeof d.strftime=="function"){return d.strftime(fmt);}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-default.on {
|
||||
background-color: #95a5a6;
|
||||
color: #f9f9f9;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
.btn{
|
||||
border-right: 1px solid rgba(0,0,0,0.09);
|
||||
|
|
@ -62,6 +67,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn, .btn-group {
|
||||
&.offset-right {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
display: inline-block;
|
||||
height: 36px;
|
||||
|
|
@ -76,12 +87,32 @@
|
|||
}
|
||||
|
||||
&:hover:before {
|
||||
color: @color-link;
|
||||
}
|
||||
|
||||
&.danger:hover:before {
|
||||
color: @color-btn-danger;
|
||||
}
|
||||
|
||||
&.pull-right:before {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.margin-left {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: 17px;
|
||||
height: 17px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
&.larger {
|
||||
font-size: 21px;
|
||||
height: 21px;
|
||||
line-height: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
|
|
|
|||
|
|
@ -224,6 +224,17 @@ label {
|
|||
}
|
||||
}
|
||||
|
||||
.field-section {
|
||||
border-bottom: 1px solid @color-form-field-border;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 7px;
|
||||
|
||||
> p:first-child,
|
||||
> h4:first-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.field-textarea {
|
||||
resize: vertical;
|
||||
&.size-tiny { min-height: @size-tiny; }
|
||||
|
|
@ -233,6 +244,12 @@ label {
|
|||
&.size-giant { min-height: @size-giant; }
|
||||
}
|
||||
|
||||
.field-checkboxlist {
|
||||
.checkbox:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.field-checkboxlist-scrollable {
|
||||
background: white;
|
||||
border: 1px solid @color-list-border;
|
||||
|
|
|
|||
|
|
@ -169,11 +169,11 @@ table.table.data {
|
|||
border-left: 3px solid @color-list-stripe-active;
|
||||
}
|
||||
}
|
||||
tr:not(.no-data):hover td {
|
||||
tr:not(.no-data):hover td, tr:not(.no-data).selected td, {
|
||||
background: @color-list-hover-bg !important;
|
||||
color: white;
|
||||
|
||||
a, span {
|
||||
a, span, i[class^="icon-"] {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
|
@ -242,6 +242,22 @@ table.table.data {
|
|||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&.icons {
|
||||
td i[class^="icon-"] {
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
font-size: 15px;
|
||||
color: #95a5a6;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
.user-select(none);
|
||||
}
|
||||
}
|
||||
|
||||
tfoot {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
table.name-value-list {
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
|
||||
th, td {
|
||||
padding: 4px 0 4px 0;
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
th, td {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
color: #95a5a6;
|
||||
padding-right: 15px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
color: #2b3e50;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
div.panel {
|
||||
@panel-border-color: #ecf0f1;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.no-padding-bottom {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&.padding-top {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
&.padding-less {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
&.transparent {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&.border-left {
|
||||
border-left: 1px solid @panel-border-color;
|
||||
}
|
||||
|
||||
&.border-right {
|
||||
border-right: 1px solid @panel-border-color;
|
||||
}
|
||||
|
||||
&.border-bottom {
|
||||
border-bottom: 1px solid @panel-border-color;
|
||||
}
|
||||
|
||||
&.triangle-down {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
.triangle(down, 15px, 8px, white);
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
bottom: -8px;
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
&:before {
|
||||
.triangle(down, 17px, 9px, #edeeef);
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
bottom: -9px;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Panel sections
|
||||
*/
|
||||
|
||||
h3.section, > label {
|
||||
text-transform: uppercase;
|
||||
color: #95a5a6;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
> label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
.nav.selector-group {
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.01em;
|
||||
|
||||
li {
|
||||
a {
|
||||
padding: 7px 20px 7px 23px;
|
||||
color: #95a5a6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-left: 3px solid #e6802b;
|
||||
padding-left: 0;
|
||||
|
||||
a {
|
||||
padding-left: 20px;
|
||||
color: #2b3e50;
|
||||
}
|
||||
}
|
||||
|
||||
i[class^="icon-"] {
|
||||
font-size: 17px;
|
||||
margin-right: 6px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.panel {
|
||||
.nav.selector-group {
|
||||
margin: 0 -20px 0 -20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,10 @@
|
|||
position: relative;
|
||||
.horizontal-scroll-indicators(@color-scroll-indicator);
|
||||
|
||||
&.standalone-paddings {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
left: -10px;
|
||||
}
|
||||
|
|
@ -22,6 +26,10 @@
|
|||
white-space: nowrap;
|
||||
margin-right: 20px;
|
||||
|
||||
&.last {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.horizontal-scroll-indicators(@color-scroll-indicator);
|
||||
|
||||
&:before { left: -10px; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
ul.tree-path {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: 1px;
|
||||
font-size: 13px;
|
||||
|
||||
&:after {
|
||||
.icon(@angle-right);
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
margin-left: 5px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
color: #95a5a6;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
a {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.go-up {
|
||||
font-size: 12px;
|
||||
margin-right: 7px;
|
||||
|
||||
a {
|
||||
color: #95a5a6;
|
||||
|
||||
&:hover {
|
||||
color: @color-link;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.root a {
|
||||
font-weight: 600;
|
||||
color: #405261;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #95a5a6;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,11 @@ body.loading, body.loading * {
|
|||
cursor: wait !important;
|
||||
}
|
||||
|
||||
body.no-select {
|
||||
.user-select(none);
|
||||
cursor: default!important;
|
||||
}
|
||||
|
||||
//
|
||||
// Layout canvas
|
||||
//
|
||||
|
|
@ -114,6 +119,10 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.whiteboard {
|
||||
background: white;
|
||||
}
|
||||
|
||||
//
|
||||
// Layout styles
|
||||
//
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@
|
|||
@import "controls/treeview.less";
|
||||
@import "controls/callout.less";
|
||||
@import "controls/sidenav-tree.less";
|
||||
@import "controls/panels.less";
|
||||
@import "controls/selector-group.less";
|
||||
@import "controls/tree-path.less";
|
||||
@import "controls/namevaluelist.less";
|
||||
|
||||
// Vendor
|
||||
@import "../vendor/sweet-alert/sweet-alert.less";
|
||||
|
|
@ -20,7 +20,14 @@ can just fix the size of their placeholders.
|
|||
* http://benalman.com/about/license/
|
||||
*/
|
||||
|
||||
(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);
|
||||
/*
|
||||
* The plugin depends on jQuery.Resize plugin https://github.com/cowboy/jquery-resize
|
||||
* which causes the memory leaking. The plugin dependency was replaced with the native
|
||||
* window.resize event as we don't need the jQuery.Resize functionality anyways.
|
||||
* -ab March 1, 2015
|
||||
*/
|
||||
|
||||
// (function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);
|
||||
|
||||
(function ($) {
|
||||
var options = { }; // no options
|
||||
|
|
@ -40,11 +47,14 @@ can just fix the size of their placeholders.
|
|||
}
|
||||
|
||||
function bindEvents(plot, eventHolder) {
|
||||
plot.getPlaceholder().resize(onResize);
|
||||
//plot.getPlaceholder().resize(onResize);
|
||||
|
||||
$(window).bind('resize', onResize)
|
||||
}
|
||||
|
||||
function shutdown(plot, eventHolder) {
|
||||
plot.getPlaceholder().unbind("resize", onResize);
|
||||
//plot.getPlaceholder().unbind("resize", onResize);
|
||||
$(window).unbind('resize', onResize)
|
||||
}
|
||||
|
||||
plot.hooks.bindEvents.push(bindEvents);
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class FilterScope
|
|||
protected function evalConfig($config)
|
||||
{
|
||||
if (isset($config['options'])) {
|
||||
$this->options($config['options']);
|
||||
$this->options = $config['options'];
|
||||
}
|
||||
if (isset($config['context'])) {
|
||||
$this->context = $config['context'];
|
||||
|
|
|
|||
|
|
@ -170,6 +170,10 @@ class Filter extends WidgetBase
|
|||
*/
|
||||
protected function getAvailableOptions($scope, $searchQuery = null)
|
||||
{
|
||||
if (count($scope->options)) {
|
||||
return $scope->options;
|
||||
}
|
||||
|
||||
$available = [];
|
||||
$nameColumn = $this->getScopeNameColumn($scope);
|
||||
$options = $this->getOptionsFromModel($scope, $searchQuery);
|
||||
|
|
@ -210,7 +214,7 @@ class Filter extends WidgetBase
|
|||
$query = $model->newQuery();
|
||||
|
||||
$this->fireEvent('filter.extendQuery', [$query, $scope]);
|
||||
|
||||
|
||||
if (!$searchQuery) {
|
||||
return $query->get();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
use App;
|
||||
use Str;
|
||||
use Lang;
|
||||
use Form as FormHelper;
|
||||
use Input;
|
||||
use Event;
|
||||
use Form as FormHelper;
|
||||
use Backend\Classes\FormTabs;
|
||||
use Backend\Classes\FormField;
|
||||
use Backend\Classes\WidgetBase;
|
||||
|
|
@ -23,7 +23,6 @@ use October\Rain\Database\Model;
|
|||
*/
|
||||
class Form extends WidgetBase
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php if (!$field->hidden): ?>
|
||||
|
||||
<?php if (in_array($field->type, ['checkbox', 'switch'])): ?>
|
||||
<?php if (in_array($field->type, ['checkbox', 'switch', 'section'])): ?>
|
||||
|
||||
<?= $this->makePartial('field_'.$field->type, ['field' => $field]) ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,85 +5,86 @@
|
|||
<!-- Checkbox List -->
|
||||
<?php if (count($fieldOptions)): ?>
|
||||
|
||||
<?php if ($this->previewMode): ?>
|
||||
<!-- Read-only -->
|
||||
<div class="field-checkboxlist">
|
||||
|
||||
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
|
||||
<?php
|
||||
$index++;
|
||||
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
|
||||
if (!in_array($value, $checkedValues)) continue;
|
||||
if (is_string($option)) $option = [$option];
|
||||
?>
|
||||
<div class="checkbox custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="<?= $checkboxId ?>"
|
||||
name="<?= $field->getName() ?>[]"
|
||||
value="<?= $value ?>"
|
||||
disabled="disabled"
|
||||
checked="checked">
|
||||
<?php if ($this->previewMode): ?>
|
||||
|
||||
<label for="<?= $checkboxId ?>">
|
||||
<?= e(trans($option[0])) ?>
|
||||
</label>
|
||||
<?php if (isset($option[1])): ?>
|
||||
<p class="help-block"><?= e(trans($option[1])) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
|
||||
<?php
|
||||
$index++;
|
||||
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
|
||||
if (!in_array($value, $checkedValues)) continue;
|
||||
if (is_string($option)) $option = [$option];
|
||||
?>
|
||||
<div class="checkbox custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="<?= $checkboxId ?>"
|
||||
name="<?= $field->getName() ?>[]"
|
||||
value="<?= $value ?>"
|
||||
disabled="disabled"
|
||||
checked="checked">
|
||||
|
||||
<?php else: ?>
|
||||
<!-- Editable -->
|
||||
|
||||
<?php if (count($fieldOptions) > 10): ?>
|
||||
<!-- Quick selection -->
|
||||
<small>
|
||||
<?= e(trans('backend::lang.form.select')) ?>:
|
||||
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', true)"><?= e(trans('backend::lang.form.select_all')) ?></a>,
|
||||
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', false)"><?= e(trans('backend::lang.form.select_none')) ?></a>
|
||||
</small>
|
||||
|
||||
<!-- Scrollable Checkbox list -->
|
||||
<div class="field-checkboxlist-scrollable" id="<?= $field->getId('scrollable') ?>">
|
||||
<div class="control-scrollbar" data-control="scrollbar">
|
||||
<?php endif ?>
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="<?= $field->getName() ?>"
|
||||
value="0" />
|
||||
|
||||
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
|
||||
<?php
|
||||
$index++;
|
||||
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
|
||||
if (is_string($option)) $option = [$option];
|
||||
?>
|
||||
<div class="checkbox custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="<?= $checkboxId ?>"
|
||||
name="<?= $field->getName() ?>[]"
|
||||
value="<?= $value ?>"
|
||||
<?= in_array($value, $checkedValues) ? 'checked="checked"' : '' ?>>
|
||||
|
||||
<label for="<?= $checkboxId ?>">
|
||||
<?= e(trans($option[0])) ?>
|
||||
</label>
|
||||
<?php if (isset($option[1])): ?>
|
||||
<p class="help-block"><?= e(trans($option[1])) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php if (count($fieldOptions) > 10): ?>
|
||||
<label for="<?= $checkboxId ?>">
|
||||
<?= e(trans($option[0])) ?>
|
||||
</label>
|
||||
<?php if (isset($option[1])): ?>
|
||||
<p class="help-block"><?= e(trans($option[1])) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<?php if (count($fieldOptions) > 10): ?>
|
||||
<!-- Quick selection -->
|
||||
<small>
|
||||
<?= e(trans('backend::lang.form.select')) ?>:
|
||||
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', true)"><?= e(trans('backend::lang.form.select_all')) ?></a>,
|
||||
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', false)"><?= e(trans('backend::lang.form.select_none')) ?></a>
|
||||
</small>
|
||||
|
||||
<!-- Scrollable Checkbox list -->
|
||||
<div class="field-checkboxlist-scrollable" id="<?= $field->getId('scrollable') ?>">
|
||||
<div class="control-scrollbar" data-control="scrollbar">
|
||||
<?php endif ?>
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="<?= $field->getName() ?>"
|
||||
value="0" />
|
||||
|
||||
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
|
||||
<?php
|
||||
$index++;
|
||||
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
|
||||
if (is_string($option)) $option = [$option];
|
||||
?>
|
||||
<div class="checkbox custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="<?= $checkboxId ?>"
|
||||
name="<?= $field->getName() ?>[]"
|
||||
value="<?= $value ?>"
|
||||
<?= in_array($value, $checkedValues) ? 'checked="checked"' : '' ?>>
|
||||
|
||||
<label for="<?= $checkboxId ?>">
|
||||
<?= e(trans($option[0])) ?>
|
||||
</label>
|
||||
<?php if (isset($option[1])): ?>
|
||||
<p class="help-block"><?= e(trans($option[1])) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php if (count($fieldOptions) > 10): ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php endif ?>
|
||||
|
||||
<?php endif ?>
|
||||
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<!-- Section -->
|
||||
<div class="field-section">
|
||||
<?php if ($field->label): ?>
|
||||
<h4><?= e(trans($field->label)) ?></h4>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($field->comment): ?>
|
||||
<p class="help-block"><?= e(trans($field->comment)) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
|
@ -83,7 +83,13 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'permissions' => ['cms.manage_pages', 'cms.manage_layouts', 'cms.manage_partials']
|
||||
]
|
||||
]
|
||||
|
||||
],
|
||||
'media' => [
|
||||
'label' => 'cms::lang.media.menu_label',
|
||||
'icon' => 'icon-folder',
|
||||
'url' => Backend::url('cms/media'),
|
||||
'permissions' => ['cms.*'],
|
||||
'order' => 20
|
||||
]
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,10 +56,8 @@ class MediaLibrary
|
|||
*/
|
||||
protected function init()
|
||||
{
|
||||
$this->storagePath = Config::get('cms.storage.media.path', '/storage/app/media');
|
||||
$this->storageDisk = Storage::disk(
|
||||
Config::get('cms.storage.media.disk', 'local'));
|
||||
$this->storageFolder = $this->validatePath(
|
||||
$this->storagePath = rtrim(Config::get('cms.storage.media.path', '/storage/app/media'), '/');
|
||||
$this->storageFolder = self::validatePath(
|
||||
Config::get('cms.storage.media.folder', 'media'), true);
|
||||
$this->ignoreNames = Config::get('cms.storage.media.ignore', $this->defaultIgnoreNames);
|
||||
|
||||
|
|
@ -74,7 +72,7 @@ class MediaLibrary
|
|||
*/
|
||||
public function listFolderContents($folder = '/', $sortBy = 'title')
|
||||
{
|
||||
$folder = $this->validatePath($folder);
|
||||
$folder = self::validatePath($folder);
|
||||
$fullFolderPath = $this->getMediaPath($folder);
|
||||
|
||||
/*
|
||||
|
|
@ -109,12 +107,28 @@ class MediaLibrary
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns URL of a Library file.
|
||||
* @param string $path Specifies a file path relative to the Library root.
|
||||
* Determines if a file with the specified path exists in the library.
|
||||
* @param string $path Specifies the file path relative the the Library root.
|
||||
* @return boolean Returns TRUE if the file exists.
|
||||
*/
|
||||
public function url($path)
|
||||
public function exists($path)
|
||||
{
|
||||
|
||||
$path = self::validatePath($path);
|
||||
$fullPath = $this->getMediaPath($path);
|
||||
|
||||
return $this->getStorageDisk()->exists($fullPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file contents.
|
||||
* @param string $path Specifies the file path relative the the Library root.
|
||||
* @return string Returns the file contents
|
||||
*/
|
||||
public function get($path)
|
||||
{
|
||||
$path = self::validatePath($path);
|
||||
$fullPath = $this->getMediaPath($path);
|
||||
return $this->getStorageDisk()->get($fullPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -137,7 +151,7 @@ class MediaLibrary
|
|||
* @param boolean $normalizeOnly Specifies if only the normalization, without validation should be performed.
|
||||
* @return string Returns a normalized path.
|
||||
*/
|
||||
protected function validatePath($path, $normalizeOnly = false)
|
||||
public static function validatePath($path, $normalizeOnly = false)
|
||||
{
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = '/'.trim($path, '/');
|
||||
|
|
@ -171,7 +185,7 @@ class MediaLibrary
|
|||
*/
|
||||
protected function getMediaRelativePath($path)
|
||||
{
|
||||
$path = $this->validatePath($path, true);
|
||||
$path = self::validatePath($path, true);
|
||||
|
||||
if (substr($path, 0, $this->storageFolderNameLength) == $this->storageFolder)
|
||||
return substr($path, $this->storageFolderNameLength);
|
||||
|
|
@ -202,10 +216,18 @@ class MediaLibrary
|
|||
if (!$this->isVisible($relativePath))
|
||||
return;
|
||||
|
||||
$lastModified = $this->storageDisk->lastModified($path);
|
||||
$size = $itemType == MediaLibraryItem::TYPE_FILE ? $this->storageDisk->size($path) : $this->getFolderItemCount($path);
|
||||
/*
|
||||
* S3 doesn't allow getting the last modified timestamp for folders,
|
||||
* so this feature is disabled - folders timestamp is always NULL.
|
||||
*/
|
||||
$lastModified = $itemType == MediaLibraryItem::TYPE_FILE ?
|
||||
$this->getStorageDisk()->lastModified($path) : null;
|
||||
|
||||
return new MediaLibraryItem($relativePath, $size, $lastModified, $itemType);
|
||||
$size = $itemType == MediaLibraryItem::TYPE_FILE ?
|
||||
$this->getStorageDisk()->size($path) : $this->getFolderItemCount($path);
|
||||
|
||||
$publicUrl = $this->storagePath.$relativePath;
|
||||
return new MediaLibraryItem($relativePath, $size, $lastModified, $itemType, $publicUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -216,8 +238,8 @@ class MediaLibrary
|
|||
protected function getFolderItemCount($path)
|
||||
{
|
||||
$folderItems = array_merge(
|
||||
$this->storageDisk->files($path),
|
||||
$this->storageDisk->directories($path));
|
||||
$this->getStorageDisk()->files($path),
|
||||
$this->getStorageDisk()->directories($path));
|
||||
|
||||
$size = 0;
|
||||
foreach ($folderItems as $folderItem) {
|
||||
|
|
@ -240,13 +262,13 @@ class MediaLibrary
|
|||
'folders' => []
|
||||
];
|
||||
|
||||
$files = $this->storageDisk->files($fullFolderPath);
|
||||
$files = $this->getStorageDisk()->files($fullFolderPath);
|
||||
foreach ($files as $file) {
|
||||
if ($libraryItem = $this->initLibraryItem($file, MediaLibraryItem::TYPE_FILE))
|
||||
$result['files'][] = $libraryItem;
|
||||
}
|
||||
|
||||
$folders = $this->storageDisk->directories($fullFolderPath);
|
||||
$folders = $this->getStorageDisk()->directories($fullFolderPath);
|
||||
foreach ($folders as $folder) {
|
||||
if ($libraryItem = $this->initLibraryItem($folder, MediaLibraryItem::TYPE_FOLDER))
|
||||
$result['folders'][] = $libraryItem;
|
||||
|
|
@ -284,4 +306,20 @@ class MediaLibrary
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns the Media Library disk.
|
||||
* This method should always be used instead of trying to access the
|
||||
* $storageDisk property directly as initializing the disc requires
|
||||
* communicating with the remote storage.
|
||||
* @return mixed Returns the storage disk object.
|
||||
*/
|
||||
protected function getStorageDisk()
|
||||
{
|
||||
if ($this->storageDisk)
|
||||
return $this->storageDisk;
|
||||
|
||||
return $this->storageDisk = Storage::disk(
|
||||
Config::get('cms.storage.media.disk', 'local'));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
<?php namespace Cms\Classes;
|
||||
|
||||
use Config;
|
||||
use File;
|
||||
|
||||
/**
|
||||
* Represents a file or folder in the Media Library.
|
||||
*
|
||||
|
|
@ -9,9 +12,13 @@
|
|||
class MediaLibraryItem
|
||||
{
|
||||
const TYPE_FILE = 'file';
|
||||
|
||||
const TYPE_FOLDER = 'folder';
|
||||
|
||||
const FILE_TYPE_IMAGE = 'image';
|
||||
const FILE_TYPE_VIDEO = 'video';
|
||||
const FILE_TYPE_AUDIO = 'audio';
|
||||
const FILE_TYPE_DOCUMENT = 'document';
|
||||
|
||||
/**
|
||||
* @var string Specifies the item path relative to the Library root.
|
||||
*/
|
||||
|
|
@ -34,16 +41,93 @@ class MediaLibraryItem
|
|||
*/
|
||||
public $type;
|
||||
|
||||
public function __construct($path, $size, $lastModified, $type)
|
||||
/**
|
||||
* @var string Specifies the public URL of the item.
|
||||
*/
|
||||
public $publicUrl;
|
||||
|
||||
/**
|
||||
* @var array Contains a default list of files and directories to ignore.
|
||||
* The list can be customized with the following configuration options:
|
||||
* - cms.storage.media.image_extensions
|
||||
* - cms.storage.media.video_extensions
|
||||
* - cms.storage.media.audo_extensions
|
||||
*/
|
||||
protected static $defaultTypeExtensions = [
|
||||
'image' => ['gif', 'png', 'jpg', 'jpeg', 'bmp'],
|
||||
'video' => ['mp4', 'avi', 'mov', 'mpg'],
|
||||
'audio' => ['mp3', 'wav', 'wma', 'm4a']
|
||||
];
|
||||
|
||||
protected static $imageExtensions;
|
||||
protected static $videoExtensions;
|
||||
protected static $audioExtensions;
|
||||
|
||||
public function __construct($path, $size, $lastModified, $type, $publicUrl)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->size = $size;
|
||||
$this->lastModified = $lastModified;
|
||||
$this->type = $type;
|
||||
$this->publicUrl = $publicUrl;
|
||||
}
|
||||
|
||||
public function isFile()
|
||||
{
|
||||
return $this->type == self::TYPE_FILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file type by its name.
|
||||
* The known file types are: image, video, audio, document
|
||||
* @return string Returns the file type or NULL if the item is a folder.
|
||||
*/
|
||||
public function getFileType()
|
||||
{
|
||||
if (!$this->isFile())
|
||||
return null;
|
||||
|
||||
if (!self::$imageExtensions) {
|
||||
self::$imageExtensions = Config::get('cms.storage.media.image_extensions', self::$defaultTypeExtensions['image']);
|
||||
self::$videoExtensions = Config::get('cms.storage.media.video_extensions', self::$defaultTypeExtensions['video']);
|
||||
self::$audioExtensions = Config::get('cms.storage.media.audio_extensions', self::$defaultTypeExtensions['audio']);
|
||||
}
|
||||
|
||||
$extension = pathinfo($this->path, PATHINFO_EXTENSION);
|
||||
if (!strlen($extension))
|
||||
return self::FILE_TYPE_DOCUMENT;
|
||||
|
||||
if (in_array($extension, self::$imageExtensions))
|
||||
return self::FILE_TYPE_IMAGE;
|
||||
|
||||
if (in_array($extension, self::$videoExtensions))
|
||||
return self::FILE_TYPE_VIDEO;
|
||||
|
||||
if (in_array($extension, self::$audioExtensions))
|
||||
return self::FILE_TYPE_AUDIO;
|
||||
|
||||
return self::FILE_TYPE_DOCUMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item size as string.
|
||||
* For file-type items the size is the number of bytes. For folder-type items
|
||||
* the size is the number of items contained by the item.
|
||||
* @return string Returns the size as string.
|
||||
*/
|
||||
public function sizeToString()
|
||||
{
|
||||
return $this->type == self::TYPE_FILE ?
|
||||
File::sizeToString($this->size) :
|
||||
$this->size.' '.trans('cms::lang.media.folder_size_items');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item last modification date as string.
|
||||
* @return string Returns the item last modification date as string.
|
||||
*/
|
||||
public function lastModifiedAsString()
|
||||
{
|
||||
return $this->lastModified ? date('M d, Y', $this->lastModified) : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php namespace Cms\Controllers;
|
||||
|
||||
use BackendMenu;
|
||||
use Backend\Classes\Controller;
|
||||
use Cms\Widgets\MediaManager;
|
||||
|
||||
/**
|
||||
* CMS Media Manager
|
||||
*
|
||||
* @package october\cms
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class Media extends Controller
|
||||
{
|
||||
public $requiredPermissions = ['cms.*'];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
BackendMenu::setContext('October.Cms', 'media', true);
|
||||
$this->pageTitle = 'cms::lang.media.menu_label';
|
||||
|
||||
$manager = new MediaManager($this, 'manager');
|
||||
$manager->bindToController();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->bodyClass = 'compact-container';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?= Block::put('head') ?><?= Block::endPut() ?>
|
||||
|
||||
<?= Block::put('body') ?>
|
||||
<div class="layout">
|
||||
<?= $this->widget->manager->render() ?>
|
||||
</div>
|
||||
<?= Block::endPut() ?>
|
||||
|
|
@ -181,6 +181,27 @@ return [
|
|||
'manage_themes' => 'Manage themes'
|
||||
],
|
||||
'media' => [
|
||||
'invalid_path' => "Invalid file path specified: ':path'."
|
||||
'invalid_path' => "Invalid file path specified: ':path'.",
|
||||
'menu_label' => 'Media',
|
||||
'upload' => 'Upload',
|
||||
'add_folder' => 'Add folder',
|
||||
'search' => 'Search',
|
||||
'filter_everything' => 'Everything',
|
||||
'filter_images' => 'Images',
|
||||
'filter_video' => 'Video',
|
||||
'filter_audio' => 'Audio',
|
||||
'filter_documents' => 'Documents',
|
||||
'library' => 'Library',
|
||||
'folder_size_items' => 'item(s)',
|
||||
'size' => 'Size',
|
||||
'title' => 'Title',
|
||||
'last_modified' => 'Last modified',
|
||||
'public_url' => 'Public URL',
|
||||
'click_here' => 'Click here',
|
||||
'thumbnail_error' => 'Error generating thumbnail.',
|
||||
'return_to_parent' => 'Return to the parent folder',
|
||||
'return_to_parent_label' => 'Go up ..',
|
||||
'nothing_selected' => 'Nothing is selected.',
|
||||
'multiple_selected' => 'Multiple items selected.'
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,444 @@
|
|||
<?php namespace Cms\Widgets;
|
||||
|
||||
use URL;
|
||||
use Str;
|
||||
use Lang;
|
||||
use File;
|
||||
use Input;
|
||||
use Request;
|
||||
use Response;
|
||||
use Exception;
|
||||
use SystemException;
|
||||
use ApplicationException;
|
||||
use Backend\Classes\WidgetBase;
|
||||
use Cms\Classes\MediaLibrary;
|
||||
use Cms\Classes\MediaLibraryItem;
|
||||
use October\Rain\Database\Attach\Resizer;
|
||||
|
||||
/**
|
||||
* Media Manager widget.
|
||||
*
|
||||
* @package october\cms
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MediaManager extends WidgetBase
|
||||
{
|
||||
const FOLDER_ROOT = '/';
|
||||
const VIEW_MODE_GRID = 'grid';
|
||||
const VIEW_MODE_LIST = 'list';
|
||||
const VIEW_MODE_TILES = 'tiles';
|
||||
|
||||
protected $brokenImageHash = null;
|
||||
|
||||
public function __construct($controller, $alias)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
|
||||
parent::__construct($controller, []);
|
||||
$this->bindToController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget.
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$this->prepareVars();
|
||||
|
||||
return $this->makePartial('body');
|
||||
}
|
||||
|
||||
/*
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
public function onSearch()
|
||||
{
|
||||
}
|
||||
|
||||
public function onGoToFolder()
|
||||
{
|
||||
$path = Input::get('path');
|
||||
|
||||
if (Input::get('clearCache'))
|
||||
MediaLibrary::instance()->resetCache();
|
||||
|
||||
$this->setCurrentFolder($path);
|
||||
$this->prepareVars();
|
||||
|
||||
return [
|
||||
'#'.$this->getId('item-list') => $this->makePartial('item-list'),
|
||||
'#'.$this->getId('folder-path') => $this->makePartial('folder-path')
|
||||
];
|
||||
}
|
||||
|
||||
public function onGenerateThumbnails()
|
||||
{
|
||||
$batch = Input::get('batch');
|
||||
if (!is_array($batch))
|
||||
return;
|
||||
|
||||
$result = [];
|
||||
foreach ($batch as $thumbnailInfo)
|
||||
$result[] = $this->generateThumbnail($thumbnailInfo);
|
||||
|
||||
return [
|
||||
'generatedThumbnails'=>$result
|
||||
];
|
||||
}
|
||||
|
||||
public function onGetSidebarThumbnail()
|
||||
{
|
||||
$path = Input::get('path');
|
||||
$lastModified = Input::get('lastModified');
|
||||
|
||||
$thumbnailParams = $this->getThumbnailParams();
|
||||
$thumbnailParams['width'] = 300;
|
||||
$thumbnailParams['height'] = 255;
|
||||
$thumbnailParams['mode'] = 'auto';
|
||||
|
||||
$path = MediaLibrary::validatePath($path);
|
||||
|
||||
if (!is_numeric($lastModified))
|
||||
throw new ApplicationException('Invalid input data');
|
||||
|
||||
// If the thumbnail file exists - just return the thumbnail marup,
|
||||
// otherwise generate a new thumbnail.
|
||||
$thumbnailPath = $this->thumbnailExists($thumbnailParams, $path, $lastModified);
|
||||
if ($thumbnailPath) {
|
||||
return [
|
||||
'markup'=>$this->makePartial('thumbnail-image', [
|
||||
'isError' => $this->thumbnailIsError($thumbnailPath),
|
||||
'imageUrl' => $this->getThumbnailImageUrl($thumbnailPath)
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
$thumbnailInfo = $thumbnailParams;
|
||||
$thumbnailInfo['path'] = $path;
|
||||
$thumbnailInfo['lastModified'] = $lastModified;
|
||||
$thumbnailInfo['id'] = 'sidebar-thumbnail';
|
||||
|
||||
return $this->generateThumbnail($thumbnailInfo, $thumbnailParams, true);
|
||||
}
|
||||
|
||||
public function onChangeView()
|
||||
{
|
||||
$viewMode = Input::get('view');
|
||||
$path = Input::get('path');
|
||||
|
||||
$this->setViewMode($viewMode);
|
||||
$this->setCurrentFolder($path);
|
||||
|
||||
$this->prepareVars();
|
||||
|
||||
return [
|
||||
'#'.$this->getId('item-list') => $this->makePartial('item-list'),
|
||||
'#'.$this->getId('folder-path') => $this->makePartial('folder-path'),
|
||||
'#'.$this->getId('view-mode-buttons') => $this->makePartial('view-mode-buttons')
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Methods for th internal use
|
||||
*/
|
||||
|
||||
protected function prepareVars()
|
||||
{
|
||||
clearstatcache();
|
||||
|
||||
$folder = $this->getCurrentFolder();
|
||||
$viewMode = $this->getViewMode();
|
||||
|
||||
$this->vars['items'] = $this->listFolderItems($folder);
|
||||
$this->vars['currentFolder'] = $folder;
|
||||
$this->vars['isRootFolder'] = $folder == self::FOLDER_ROOT;
|
||||
$this->vars['pathSegments'] = $this->splitPathToSegments($folder);
|
||||
$this->vars['viewMode'] = $viewMode;
|
||||
|
||||
$this->vars['thumbnailParams'] = $this->getThumbnailParams($viewMode);
|
||||
}
|
||||
|
||||
protected function listFolderItems($folder)
|
||||
{
|
||||
return MediaLibrary::instance()->listFolderContents($folder);
|
||||
}
|
||||
|
||||
protected function getCurrentFolder()
|
||||
{
|
||||
$folder = $this->getSession('media_folder', self::FOLDER_ROOT);
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
protected function setCurrentFolder($path)
|
||||
{
|
||||
$path = MediaLibrary::validatePath($path);
|
||||
|
||||
$this->putSession('media_folder', $path);
|
||||
}
|
||||
|
||||
protected function itemTypeToIconClass($item, $itemType)
|
||||
{
|
||||
if ($item->type == MediaLibraryItem::TYPE_FOLDER)
|
||||
return 'icon-folder';
|
||||
|
||||
switch ($itemType) {
|
||||
case MediaLibraryItem::FILE_TYPE_IMAGE : return "icon-picture-o";
|
||||
case MediaLibraryItem::FILE_TYPE_VIDEO : return "icon-video-camera";
|
||||
case MediaLibraryItem::FILE_TYPE_AUDIO : return "icon-volume-up";
|
||||
default : return "icon-file";
|
||||
}
|
||||
}
|
||||
|
||||
protected function splitPathToSegments($path)
|
||||
{
|
||||
$path = MediaLibrary::validatePath($path, true);
|
||||
$result = [];
|
||||
|
||||
do {
|
||||
$result[] = $path;
|
||||
} while (($path = dirname($path)) != '/');
|
||||
|
||||
return array_reverse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds widget specific asset files. Use $this->addJs() and $this->addCss()
|
||||
* to register new assets to include on the page.
|
||||
* @return void
|
||||
*/
|
||||
protected function loadAssets()
|
||||
{
|
||||
$this->addCss('css/mediamanager.css', 'core');
|
||||
$this->addJs('js/mediamanager.js', 'core');
|
||||
}
|
||||
|
||||
protected function getViewMode()
|
||||
{
|
||||
return $this->getSession('view_mode', self::VIEW_MODE_GRID);
|
||||
}
|
||||
|
||||
protected function setViewMode($viewMode)
|
||||
{
|
||||
if (!in_array($viewMode, [self::VIEW_MODE_GRID, self::VIEW_MODE_LIST, self::VIEW_MODE_TILES]))
|
||||
throw new SystemException('Invalid input data');
|
||||
|
||||
return $this->putSession('view_mode', $viewMode);
|
||||
}
|
||||
|
||||
protected function getThumbnailParams($viewMode = null)
|
||||
{
|
||||
$result = [
|
||||
'mode' => 'crop',
|
||||
'ext' => 'png'
|
||||
];
|
||||
|
||||
if ($viewMode) {
|
||||
if ($viewMode == self::VIEW_MODE_LIST) {
|
||||
$result['width'] = 75;
|
||||
$result['height'] = 75;
|
||||
}
|
||||
else {
|
||||
$result['width'] = 165;
|
||||
$result['height'] = 165;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getThumbnailImagePath($thumbnailParams, $itemPath, $lastModified)
|
||||
{
|
||||
$itemSignature = md5($itemPath).$lastModified;
|
||||
|
||||
$thumbFile = 'thumb_' .
|
||||
$itemSignature . '_' .
|
||||
$thumbnailParams['width'] . 'x' .
|
||||
$thumbnailParams['height'] . '_' .
|
||||
$thumbnailParams['mode'] . '.' .
|
||||
$thumbnailParams['ext'];
|
||||
|
||||
$partition = implode('/', array_slice(str_split($itemSignature, 3), 0, 3)) . '/';
|
||||
|
||||
$result = $this->getThumbnailDirectory().$partition.$thumbFile;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getThumbnailImageUrl($imagePath)
|
||||
{
|
||||
return URL::to('/storage/temp'.$imagePath);
|
||||
}
|
||||
|
||||
protected function thumbnailExists($thumbnailParams, $itemPath, $lastModified)
|
||||
{
|
||||
$thumbnailPath = $this->getThumbnailImagePath($thumbnailParams, $itemPath, $lastModified);
|
||||
|
||||
$fullPath = temp_path(ltrim($thumbnailPath, '/'));
|
||||
if (File::exists($fullPath))
|
||||
return $thumbnailPath;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function thumbnailIsError($thumbnailPath)
|
||||
{
|
||||
$fullPath = temp_path(ltrim($thumbnailPath, '/'));
|
||||
return hash_file('crc32', $fullPath) == $this->getBrokenImageHash();
|
||||
}
|
||||
|
||||
protected function getLocalTempFilePath($fileName)
|
||||
{
|
||||
$fileName = md5($fileName.uniqid().microtime());
|
||||
|
||||
$path = temp_path() . '/media';
|
||||
|
||||
if (!File::isDirectory($path))
|
||||
File::makeDirectory($path, 0777, true, true);
|
||||
|
||||
return $path.'/'.$fileName;
|
||||
}
|
||||
|
||||
protected function getThumbnailDirectory()
|
||||
{
|
||||
return '/public/';
|
||||
}
|
||||
|
||||
protected function getPlaceholderId($item)
|
||||
{
|
||||
return 'placeholder'.md5($item->path.'-'.$item->lastModified.uniqid(microtime()));
|
||||
}
|
||||
|
||||
protected function generateThumbnail($thumbnailInfo, $thumbnailParams = null)
|
||||
{
|
||||
$tempFilePath = null;
|
||||
$fullThumbnailPath = null;
|
||||
$thumbnailPath = null;
|
||||
$markup = null;
|
||||
|
||||
try {
|
||||
// Get and validate input data
|
||||
$path = $thumbnailInfo['path'];
|
||||
$width = $thumbnailInfo['width'];
|
||||
$height = $thumbnailInfo['height'];
|
||||
$lastModified = $thumbnailInfo['lastModified'];
|
||||
|
||||
if (!is_numeric($width) || !is_numeric($height) || !is_numeric($lastModified))
|
||||
throw new ApplicationException('Invalid input data');
|
||||
|
||||
if (!$thumbnailParams) {
|
||||
$thumbnailParams = $this->getThumbnailParams();
|
||||
$thumbnailParams['width'] = $width;
|
||||
$thumbnailParams['height'] = $height;
|
||||
}
|
||||
|
||||
$thumbnailPath = $this->getThumbnailImagePath($thumbnailParams, $path, $lastModified);
|
||||
$fullThumbnailPath = temp_path(ltrim($thumbnailPath, '/'));
|
||||
|
||||
// Save the file locally
|
||||
$library = MediaLibrary::instance();
|
||||
$tempFilePath = $this->getLocalTempFilePath($path);
|
||||
|
||||
if (!@File::put($tempFilePath, $library->get($path)))
|
||||
throw new SystemException('Error saving remote file to a temporary location');
|
||||
|
||||
// Resize the thumbnail and save to the thumbnails directory
|
||||
$this->resizeImage($fullThumbnailPath, $thumbnailParams, $tempFilePath);
|
||||
|
||||
// Delete the temporary file
|
||||
File::delete($tempFilePath);
|
||||
$markup = $this->makePartial('thumbnail-image', [
|
||||
'isError' => false,
|
||||
'imageUrl' => $this->getThumbnailImageUrl($thumbnailPath)
|
||||
]);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
if ($tempFilePath)
|
||||
File::delete($tempFilePath);
|
||||
|
||||
if ($fullThumbnailPath)
|
||||
$this->copyBrokenImage($fullThumbnailPath);
|
||||
|
||||
$markup = $this->makePartial('thumbnail-image', ['isError' => true]);
|
||||
|
||||
// TODO: We need to log all types of exceptions here
|
||||
traceLog($ex->getMessage());
|
||||
}
|
||||
|
||||
if ($markup && ($id = $thumbnailInfo['id']))
|
||||
return [
|
||||
'id'=>$id,
|
||||
'markup'=>$markup
|
||||
];
|
||||
}
|
||||
|
||||
protected function resizeImage($fullThumbnailPath, $thumbnailParams, $tempFilePath)
|
||||
{
|
||||
$thumbnailDir = dirname($fullThumbnailPath);
|
||||
if (!File::isDirectory($thumbnailDir)) {
|
||||
if (File::makeDirectory($thumbnailDir, 0777, true) === false)
|
||||
throw new SystemException('Error creating thumbnail directory');
|
||||
}
|
||||
|
||||
$targetDimensions = $this->getTargetDimensions($thumbnailParams['width'], $thumbnailParams['height'], $tempFilePath);
|
||||
|
||||
$targetWidth = $targetDimensions[0];
|
||||
$targetHeight = $targetDimensions[1];
|
||||
|
||||
$resizer = Resizer::open($tempFilePath);
|
||||
$resizer->resize($targetWidth, $targetHeight, $thumbnailParams['mode'], [0, 0]);
|
||||
$resizer->save($fullThumbnailPath, 95);
|
||||
|
||||
File::chmod($fullThumbnailPath);
|
||||
}
|
||||
|
||||
protected function getBrokenImagePath()
|
||||
{
|
||||
return __DIR__.'/mediamanager/assets/images/broken-thumbnail.gif';
|
||||
}
|
||||
|
||||
protected function getBrokenImageHash()
|
||||
{
|
||||
if ($this->brokenImageHash)
|
||||
return $this->brokenImageHash;
|
||||
|
||||
$fullPath = $this->getBrokenImagePath();
|
||||
return $this->brokenImageHash = hash_file('crc32', $fullPath);
|
||||
}
|
||||
|
||||
protected function copyBrokenImage($path)
|
||||
{
|
||||
try {
|
||||
$thumbnailDir = dirname($path);
|
||||
if (!File::isDirectory($thumbnailDir)) {
|
||||
if (File::makeDirectory($thumbnailDir, 0777, true) === false)
|
||||
return;
|
||||
}
|
||||
File::copy($this->getBrokenImagePath(), $path);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
traceLog($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getTargetDimensions($width, $height, $originalImagePath)
|
||||
{
|
||||
$originalDimensions = [$width, $height];
|
||||
|
||||
try {
|
||||
$dimensions = getimagesize($originalImagePath);
|
||||
if (!$dimensions)
|
||||
return $originalDimensions;
|
||||
|
||||
if ($dimensions[0] > $width || $dimensions[1] > $height)
|
||||
return $originalDimensions;
|
||||
|
||||
return $dimensions;
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
return $originalDimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
div[data-control="media-manager"] audio,
|
||||
div[data-control="media-manager"] video {
|
||||
width: 100%;
|
||||
}
|
||||
div[data-control="media-manager"] video {
|
||||
background: #ecf0f1;
|
||||
max-height: 225px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-player-fallback {
|
||||
font-size: 13px;
|
||||
color: #95a5a6;
|
||||
background: #ecf0f1;
|
||||
line-height: 180%;
|
||||
}
|
||||
div[data-control="media-manager"] .media-player-fallback.panel-embedded {
|
||||
padding: 20px;
|
||||
margin: -20px -20px 0 -20px;
|
||||
}
|
||||
div[data-control="media-manager"] p.thumbnail-error-message {
|
||||
font-size: 12px;
|
||||
margin-top: 25px;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list {
|
||||
padding: 0 0 20px 20px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 0 20px 20px 0;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .icon-container {
|
||||
display: table;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .icon-container i {
|
||||
color: #95a5a6;
|
||||
display: inline-block;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .icon-container div {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .icon-container.image > div.icon-wrapper {
|
||||
display: none;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li h4 {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #2b3e50;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 15px;
|
||||
line-height: 150%;
|
||||
margin: 15px 0 5px 0;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li p.size {
|
||||
font-size: 12px;
|
||||
color: #95a5a6;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .image-placeholder {
|
||||
position: relative;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .image-placeholder i {
|
||||
padding-top: 0;
|
||||
padding-left: 2px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .image-placeholder[data-loading] i {
|
||||
display: none;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li .image-placeholder[data-loading]:after {
|
||||
background-image: url(../../../../../../modules/backend/assets/images/loading-indicator-transparent.svg);
|
||||
background-position: 50% 50%;
|
||||
content: ' ';
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
background-size: 28px 28px;
|
||||
position: absolute;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -14px;
|
||||
margin-left: -14px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li i.icon-chain-broken {
|
||||
padding: 0;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list li[data-item-type=folder] i {
|
||||
color: #4da7e8;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li {
|
||||
height: 75px;
|
||||
width: 260px;
|
||||
border: 1px solid #ecf0f1;
|
||||
background: #f6f8f9;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li .icon-container {
|
||||
border-right: 1px solid #f6f8f9;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
float: left;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li .icon-container i {
|
||||
font-size: 35px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li .icon-container.image {
|
||||
border-right: 1px solid #ecf0f1!important;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li .icon-container p.thumbnail-error-message {
|
||||
display: none;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list .icon-wrapper {
|
||||
width: 75px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li .info {
|
||||
margin-left: 90px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li .image-placeholder {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li[data-root] h4 {
|
||||
margin-top: 27px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li.selected {
|
||||
background: #4da7e8 !important;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li.selected i,
|
||||
div[data-control="media-manager"] .media-list.list li.selected p.size {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li.selected h4 {
|
||||
color: white;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.list li.selected .icon-container {
|
||||
border-right-color: #4da7e8 !important;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li {
|
||||
width: 167px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles .icon-wrapper {
|
||||
width: 167px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li .image-placeholder {
|
||||
width: 165px;
|
||||
height: 165px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li .image-placeholder[data-loading]:after {
|
||||
background-image: url(../../../../../../modules/backend/assets/images/loading-indicator-transparent.svg);
|
||||
background-position: 50% 50%;
|
||||
content: ' ';
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
background-size: 55px 55px;
|
||||
position: absolute;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -27.5px;
|
||||
margin-left: -27.5px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li .icon-container {
|
||||
width: 167px;
|
||||
height: 167px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #ecf0f1;
|
||||
overflow: hidden;
|
||||
background: #f6f8f9;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li .icon-container i {
|
||||
font-size: 55px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li .icon-container p {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li.selected .icon-container {
|
||||
background: #4da7e8 !important;
|
||||
border-color: #2581b8;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li.selected .icon-container i,
|
||||
div[data-control="media-manager"] .media-list.tiles li.selected .icon-container p {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles li.selected h4 {
|
||||
color: #2581b8;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles i.icon-chain-broken {
|
||||
margin-top: 47px;
|
||||
}
|
||||
div[data-control="media-manager"] .media-list.tiles p.size {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder-container {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder {
|
||||
display: table-cell;
|
||||
height: 225px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
}
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder[data-loading] {
|
||||
background: #ecf0f1;
|
||||
}
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder[data-loading]:after {
|
||||
background-image: url(../../../../../../modules/backend/assets/images/loading-indicator-transparent.svg);
|
||||
background-position: 50% 50%;
|
||||
content: ' ';
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
background-size: 62px 62px;
|
||||
position: absolute;
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -31px;
|
||||
margin-left: -31px;
|
||||
}
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder i.icon-chain-broken,
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder i.icon-crop,
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder i.icon-asterisk {
|
||||
color: #bdc3c7;
|
||||
font-size: 55px;
|
||||
}
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder.no-border {
|
||||
border-bottom: none;
|
||||
}
|
||||
div[data-control="media-manager"] .sidebar-image-placeholder p {
|
||||
font-size: 12px;
|
||||
margin-top: 25px;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
div[data-control="media-manager"] .list-container {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
div[data-control="media-manager"] [data-control="item-list"] {
|
||||
position: relative;
|
||||
}
|
||||
div[data-control="media-manager"] div[data-control="selection-marker"] {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
border: 1px dashed #95a5a6;
|
||||
}
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover .icon-container {
|
||||
background: #4da7e8 !important;
|
||||
border-color: #2581b8;
|
||||
}
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover .icon-container i,
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover .icon-container p {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover h4 {
|
||||
color: #2581b8;
|
||||
}
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list .list li:hover {
|
||||
background: #4da7e8 !important;
|
||||
}
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list .list li:hover i,
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list .list li:hover p.size {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list .list li:hover h4 {
|
||||
color: white;
|
||||
}
|
||||
body:not(.no-select) div[data-control="media-manager"] .media-list .list li:hover .icon-container {
|
||||
border-right-color: #4da7e8 !important;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
|
|
@ -0,0 +1,665 @@
|
|||
/*
|
||||
* Media manager control class
|
||||
*
|
||||
* Dependences:
|
||||
* - Scrollbar (october.scrollbar.js)
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
// MEDIA MANAGER CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var MediaManager = function(element, options) {
|
||||
this.$el = $(element)
|
||||
this.$form = this.$el.closest('form')
|
||||
|
||||
this.options = options
|
||||
|
||||
// Event handlers
|
||||
this.navigateHandler = this.onNavigate.bind(this)
|
||||
this.commandClickHandler = this.onCommandClick.bind(this)
|
||||
this.itemClickHandler = this.onItemClick.bind(this)
|
||||
this.listMouseDownHandler = this.onListMouseDown.bind(this)
|
||||
this.listMouseUpHandler = this.onListMouseUp.bind(this)
|
||||
this.listMouseMoveHandler = this.onListMouseMove.bind(this)
|
||||
|
||||
// Instance-bound methods
|
||||
this.updateSidebarPreviewBound = this.updateSidebarPreview.bind(this)
|
||||
this.replacePlaceholderBound = this.replacePlaceholder.bind(this)
|
||||
this.placeholdersUpdatedBound = this.placeholdersUpdated.bind(this)
|
||||
this.afterNavigateBound = this.afterNavigate.bind(this)
|
||||
this.releaseSidebarThumbnailAjaxBound = this.releaseSidebarThumbnailAjax.bind(this)
|
||||
this.replaceSidebarPlaceholderBound = this.replaceSidebarPlaceholder.bind(this)
|
||||
|
||||
// State properties
|
||||
this.selectTimer = null
|
||||
this.sidebarPreviewElement = null
|
||||
this.itemListElement = null
|
||||
this.thumbnailQueue = []
|
||||
this.activeThumbnailQueueLength = 0
|
||||
this.sidebarThumbnailAjax = null
|
||||
this.selectionMarker = null
|
||||
this.dropzone = null
|
||||
|
||||
//
|
||||
// Initialization
|
||||
//
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
MediaManager.prototype.dispose = function() {
|
||||
this.unregisterHandlers()
|
||||
this.clearSelectTimer()
|
||||
|
||||
this.$el = null
|
||||
this.$form = null
|
||||
this.updateSidebarPreviewBound = null
|
||||
this.replacePlaceholderBound = null
|
||||
this.placeholdersUpdatedBound = null
|
||||
this.sidebarPreviewElement = null
|
||||
this.itemListElement = null
|
||||
this.afterNavigateBound = null
|
||||
this.replaceSidebarPlaceholderBound = null
|
||||
this.sidebarThumbnailAjax = null
|
||||
this.selectionMarker = null
|
||||
this.thumbnailQueue = []
|
||||
}
|
||||
|
||||
// MEDIA MANAGER INTERNAL METHODS
|
||||
// ============================
|
||||
|
||||
MediaManager.prototype.init = function() {
|
||||
this.itemListElement = this.$el.find('[data-control="item-list"]').get(0)
|
||||
this.registerHandlers()
|
||||
|
||||
this.updateSidebarPreview()
|
||||
this.generateThumbnails()
|
||||
this.initUploader()
|
||||
}
|
||||
|
||||
MediaManager.prototype.registerHandlers = function() {
|
||||
this.$el.on('dblclick', this.navigateHandler)
|
||||
this.$el.on('click.tree-path', 'ul.tree-path', this.navigateHandler)
|
||||
this.$el.on('click.command', '[data-command]', this.commandClickHandler)
|
||||
this.$el.on('click.item', '[data-type="media-item"]', this.itemClickHandler)
|
||||
|
||||
if (this.itemListElement)
|
||||
this.itemListElement.addEventListener('mousedown', this.listMouseDownHandler)
|
||||
}
|
||||
|
||||
MediaManager.prototype.unregisterHandlers = function() {
|
||||
this.$el.off('dblclick', this.navigateHandler)
|
||||
this.$el.off('click.tree-path', this.navigateHandler)
|
||||
this.$el.off('click.command', this.commandClickHandler)
|
||||
this.$el.off('click.item', this.itemClickHandler)
|
||||
|
||||
if (this.itemListElement) {
|
||||
this.itemListElement.removeEventListener('mousedown', this.listMouseDownHandler)
|
||||
this.itemListElement.removeEventListener('mousemove', this.listMouseMoveHandler)
|
||||
}
|
||||
document.removeEventListener('mouseup', this.listMouseUpHandler)
|
||||
|
||||
this.navigateHandler = null
|
||||
this.commandClickHandler = null
|
||||
this.itemClickHandler = null
|
||||
this.listMouseDownHandler = null
|
||||
this.listMouseUpHandler = null
|
||||
this.listMouseMoveHandler = null
|
||||
}
|
||||
|
||||
MediaManager.prototype.changeView = function(view) {
|
||||
$.oc.stripeLoadIndicator.show()
|
||||
|
||||
var data = {
|
||||
view: view,
|
||||
path: this.$el.find('[data-type="current-folder"]').val()
|
||||
}
|
||||
|
||||
this.$form.request(this.options.alias+'::onChangeView', {
|
||||
data: data
|
||||
}).always(function() {
|
||||
$.oc.stripeLoadIndicator.hide()
|
||||
}).done(this.afterNavigateBound)
|
||||
}
|
||||
|
||||
/*
|
||||
* Selecting
|
||||
*/
|
||||
|
||||
MediaManager.prototype.clearSelectTimer = function() {
|
||||
if (this.selectTimer == null)
|
||||
return
|
||||
|
||||
clearTimeout(this.selectTimer)
|
||||
this.selectTimer = null
|
||||
}
|
||||
|
||||
MediaManager.prototype.selectItem = function(node, expandSelection) {
|
||||
if (!expandSelection) {
|
||||
var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected')
|
||||
|
||||
// The class attribute is used only for selecting, it's safe to clear it
|
||||
for (var i = 0, len = items.length; i < len; i++)
|
||||
items[i].setAttribute('class', '')
|
||||
}
|
||||
|
||||
if (!expandSelection)
|
||||
node.setAttribute('class', 'selected')
|
||||
else {
|
||||
if (node.getAttribute('class') == 'selected')
|
||||
node.setAttribute('class', '')
|
||||
else
|
||||
node.setAttribute('class', 'selected')
|
||||
}
|
||||
|
||||
this.clearSelectTimer()
|
||||
|
||||
if (this.isPreviewSidebarVisible()) {
|
||||
// Use the timeout to prevent too many AJAX requests
|
||||
// when the selection changes too quickly (with the keyboard arrows)
|
||||
this.selectTimer = setTimeout(this.updateSidebarPreviewBound, 100)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Navigation
|
||||
*/
|
||||
|
||||
MediaManager.prototype.gotoFolder = function(path, clearCache) {
|
||||
var data = {
|
||||
path: path
|
||||
}
|
||||
|
||||
if (clearCache)
|
||||
data.clearCache = true
|
||||
|
||||
$.oc.stripeLoadIndicator.show()
|
||||
this.$form.request(this.options.alias+'::onGoToFolder', {
|
||||
data: data
|
||||
}).always(function() {
|
||||
$.oc.stripeLoadIndicator.hide()
|
||||
}).done(this.afterNavigateBound)
|
||||
}
|
||||
|
||||
MediaManager.prototype.afterNavigate = function() {
|
||||
this.generateThumbnails()
|
||||
this.updateSidebarPreview(true)
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
MediaManager.prototype.isPreviewSidebarVisible = function() {
|
||||
return true
|
||||
}
|
||||
|
||||
MediaManager.prototype.updateSidebarMediaPreview = function(items) {
|
||||
var previewPanel = this.sidebarPreviewElement,
|
||||
previewContainer = previewPanel.querySelector('[data-control="media-preview-container"]'),
|
||||
template = ''
|
||||
|
||||
for (var i = 0, len = previewContainer.children.length; i < len; i++)
|
||||
previewContainer.removeChild(previewContainer.children[i])
|
||||
|
||||
if (items.length == 1) {
|
||||
var item = items[0],
|
||||
documentType = item.getAttribute('data-document-type')
|
||||
|
||||
switch (documentType) {
|
||||
case 'audio' :
|
||||
template = previewPanel.querySelector('[data-control="audio-template"]').innerHTML
|
||||
break;
|
||||
case 'video' :
|
||||
template = previewPanel.querySelector('[data-control="video-template"]').innerHTML
|
||||
break;
|
||||
case 'image' :
|
||||
template = previewPanel.querySelector('[data-control="image-template"]').innerHTML
|
||||
break;
|
||||
}
|
||||
|
||||
previewContainer.innerHTML = template
|
||||
.replace('{src}', item.getAttribute('data-public-url'))
|
||||
.replace('{path}', item.getAttribute('data-path'))
|
||||
.replace('{last-modified}', item.getAttribute('data-last-modified-ts'))
|
||||
|
||||
if (documentType == 'image')
|
||||
this.loadSidebarThumbnail()
|
||||
}
|
||||
else if (items.length == 0) {
|
||||
template = previewPanel.querySelector('[data-control="no-selection-template"]').innerHTML
|
||||
previewContainer.innerHTML = template
|
||||
}
|
||||
else {
|
||||
template = previewPanel.querySelector('[data-control="multi-selection-template"]').innerHTML
|
||||
previewContainer.innerHTML = template
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager.prototype.updateSidebarPreview = function(resetSidebar) {
|
||||
if (!this.sidebarPreviewElement)
|
||||
this.sidebarPreviewElement = this.$el.get(0).querySelector('[data-control="preview-sidebar"]')
|
||||
|
||||
var items = resetSidebar === undefined ? this.$el.get(0).querySelectorAll('[data-type="media-item"].selected') : [],
|
||||
previewPanel = this.sidebarPreviewElement
|
||||
|
||||
if (items.length == 0) {
|
||||
// No items are selected
|
||||
this.sidebarPreviewElement.querySelector('[data-control="sidebar-labels"]').setAttribute('class', 'hide')
|
||||
}
|
||||
else if (items.length == 1) {
|
||||
this.sidebarPreviewElement.querySelector('[data-control="sidebar-labels"]').setAttribute('class', 'panel')
|
||||
|
||||
// One item is selected - display the details
|
||||
var item = items[0]
|
||||
|
||||
previewPanel.querySelector('[data-label="size"]').textContent = item.getAttribute('data-size')
|
||||
previewPanel.querySelector('[data-label="title"]').textContent = item.getAttribute('data-title')
|
||||
previewPanel.querySelector('[data-label="last-modified"]').textContent = item.getAttribute('data-last-modified')
|
||||
previewPanel.querySelector('[data-label="public-url"]').setAttribute('href', item.getAttribute('data-public-url'))
|
||||
}
|
||||
else {
|
||||
// Multiple items are selected
|
||||
this.sidebarPreviewElement.querySelector('[data-control="sidebar-labels"]').setAttribute('class', 'hide')
|
||||
}
|
||||
|
||||
this.updateSidebarMediaPreview(items)
|
||||
}
|
||||
|
||||
MediaManager.prototype.loadSidebarThumbnail = function() {
|
||||
if (this.sidebarThumbnailAjax) {
|
||||
try {
|
||||
this.sidebarThumbnailAjax.abort()
|
||||
}
|
||||
catch (e) {}
|
||||
this.sidebarThumbnailAjax = null
|
||||
}
|
||||
|
||||
var sidebarThumbnail = this.sidebarPreviewElement.querySelector('[data-control="sidebar-thumbnail"]')
|
||||
if (!sidebarThumbnail)
|
||||
return
|
||||
|
||||
var data = {
|
||||
path: sidebarThumbnail.getAttribute('data-path'),
|
||||
lastModified: sidebarThumbnail.getAttribute('data-last-modified')
|
||||
}
|
||||
|
||||
this.sidebarThumbnailAjax = this.$form.request(this.options.alias+'::onGetSidebarThumbnail', {
|
||||
data: data
|
||||
})
|
||||
.done(this.replaceSidebarPlaceholderBound)
|
||||
.always(this.releaseSidebarThumbnailAjaxBound)
|
||||
}
|
||||
|
||||
MediaManager.prototype.replaceSidebarPlaceholder = function(response) {
|
||||
var sidebarThumbnail = this.sidebarPreviewElement.querySelector('[data-control="sidebar-thumbnail"]')
|
||||
if (!sidebarThumbnail)
|
||||
return
|
||||
|
||||
if (!response.markup)
|
||||
return
|
||||
|
||||
sidebarThumbnail.innerHTML = response.markup
|
||||
sidebarThumbnail.removeAttribute('data-loading')
|
||||
}
|
||||
|
||||
MediaManager.prototype.releaseSidebarThumbnailAjax = function() {
|
||||
this.sidebarThumbnailAjax = null
|
||||
}
|
||||
|
||||
/*
|
||||
* Thumbnails
|
||||
*/
|
||||
|
||||
MediaManager.prototype.generateThumbnails = function() {
|
||||
this.thumbnailQueue = []
|
||||
|
||||
var placeholders = this.itemListElement.querySelectorAll('[data-type="media-item"] div.image-placeholder')
|
||||
for (var i = (placeholders.length-1); i >= 0; i--)
|
||||
this.thumbnailQueue.push({
|
||||
id: placeholders[i].getAttribute('id'),
|
||||
width: placeholders[i].getAttribute('data-width'),
|
||||
height: placeholders[i].getAttribute('data-height'),
|
||||
path: placeholders[i].getAttribute('data-path'),
|
||||
lastModified: placeholders[i].getAttribute('data-last-modified')
|
||||
})
|
||||
|
||||
this.handleThumbnailQueue()
|
||||
}
|
||||
|
||||
MediaManager.prototype.handleThumbnailQueue = function() {
|
||||
var maxThumbnailQueueLength = 2,
|
||||
maxThumbnailBatchLength = 3
|
||||
|
||||
if (this.activeThumbnailQueueLength >= maxThumbnailQueueLength)
|
||||
return
|
||||
|
||||
for (var i = this.activeThumbnailQueueLength; i < maxThumbnailQueueLength && this.thumbnailQueue.length > 0; i++) {
|
||||
var batch = []
|
||||
|
||||
for (var j = 0; j < maxThumbnailBatchLength && this.thumbnailQueue.length > 0; j++)
|
||||
batch.push(this.thumbnailQueue.pop())
|
||||
|
||||
this.activeThumbnailQueueLength++
|
||||
|
||||
this.handleThumbnailBatch(batch).always(this.placeholdersUpdatedBound)
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager.prototype.handleThumbnailBatch = function(batch) {
|
||||
var data = {
|
||||
batch: batch
|
||||
}
|
||||
|
||||
for (var i = 0, len = batch.length; i < len; i++) {
|
||||
var placeholder = document.getElementById(batch[i].id)
|
||||
if (placeholder)
|
||||
placeholder.setAttribute('data-loading', 'true')
|
||||
}
|
||||
|
||||
var promise = this.$form.request(this.options.alias+'::onGenerateThumbnails', {
|
||||
data: data
|
||||
})
|
||||
|
||||
promise.done(this.replacePlaceholderBound)
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
MediaManager.prototype.replacePlaceholder = function(response) {
|
||||
if (!response.generatedThumbnails)
|
||||
return
|
||||
|
||||
for (var i = 0, len = response.generatedThumbnails.length; i < len; i++) {
|
||||
var thumbnailInfo = response.generatedThumbnails[i]
|
||||
|
||||
if (!thumbnailInfo.id || !thumbnailInfo.markup)
|
||||
continue
|
||||
|
||||
var node = document.getElementById(thumbnailInfo.id)
|
||||
if (!node)
|
||||
continue
|
||||
|
||||
var placeholderContainer = node.parentNode
|
||||
if (placeholderContainer)
|
||||
placeholderContainer.innerHTML = thumbnailInfo.markup
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager.prototype.placeholdersUpdated = function() {
|
||||
this.activeThumbnailQueueLength--
|
||||
|
||||
this.handleThumbnailQueue()
|
||||
}
|
||||
|
||||
/*
|
||||
* Drag-select
|
||||
*/
|
||||
|
||||
MediaManager.prototype.getAbsolutePosition = function(element) {
|
||||
// TODO: refactor to a core library
|
||||
|
||||
var top = document.body.scrollTop,
|
||||
left = 0
|
||||
|
||||
do {
|
||||
top += element.offsetTop || 0;
|
||||
top -= element.scrollTop || 0;
|
||||
left += element.offsetLeft || 0;
|
||||
element = element.offsetParent;
|
||||
} while(element)
|
||||
|
||||
return {
|
||||
top: top,
|
||||
left: left
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager.prototype.getEventPagePosition = function(ev) {
|
||||
// TODO: refactor to a core library
|
||||
|
||||
if (ev.pageX || ev.pageY) {
|
||||
return {
|
||||
x: ev.pageX,
|
||||
y: ev.pageY
|
||||
}
|
||||
}
|
||||
else if (ev.clientX || ev.clientY) {
|
||||
return {
|
||||
x: (ev.clientX + document.body.scrollLeft + document.documentElement.scrollLeft),
|
||||
y: (ev.clientY + document.body.scrollTop + document.documentElement.scrollTop)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager.prototype.getRelativePosition = function(element, pageX, pageY) {
|
||||
var absolutePosition = this.getAbsolutePosition(element)
|
||||
|
||||
return {
|
||||
x: (pageX - absolutePosition.left),
|
||||
y: (pageY - absolutePosition.top)
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager.prototype.createSelectionMarker = function() {
|
||||
if (this.selectionMarker)
|
||||
return
|
||||
|
||||
this.selectionMarker = document.createElement('div')
|
||||
this.selectionMarker.setAttribute('data-control', 'selection-marker')
|
||||
|
||||
this.itemListElement.insertBefore(this.selectionMarker, this.itemListElement.firstChild)
|
||||
}
|
||||
|
||||
MediaManager.prototype.doObjectsCollide = function(aTop, aLeft, aWidth, aHeight, bTop, bLeft, bWidth, bHeight) {
|
||||
return !(
|
||||
((aTop + aHeight) < (bTop)) ||
|
||||
(aTop > (bTop + bHeight)) ||
|
||||
((aLeft + aWidth) < bLeft) ||
|
||||
(aLeft > (bLeft + bWidth))
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Uploading
|
||||
*/
|
||||
|
||||
MediaManager.prototype.initUploader = function() {
|
||||
if (this.itemListElement) {
|
||||
// this.dropzone = new Dropzone(this.itemListElement, uploaderOptions)
|
||||
}
|
||||
|
||||
|
||||
// disable
|
||||
// dropzone.on('error', $.proxy(self.onUploadFail, self))
|
||||
// dropzone.on('success', $.proxy(self.onUploadSuccess, self))
|
||||
// dropzone.on('complete', $.proxy(self.onUploadComplete, self))
|
||||
// dropzone.on('sending', function(file, xhr, formData) {
|
||||
// $.each(self.$form.serializeArray(), function (index, field) {
|
||||
// formData.append(field.name, field.value)
|
||||
// })
|
||||
// self.onUploadStart()
|
||||
// })
|
||||
}
|
||||
|
||||
// EVENT HANDLERS
|
||||
// ============================
|
||||
|
||||
MediaManager.prototype.onNavigate = function(ev) {
|
||||
var $item = $(ev.target).closest('[data-type="media-item"]')
|
||||
|
||||
if (!$item.length || !$item.data('path').length)
|
||||
return
|
||||
|
||||
if ($item.data('item-type') == 'folder')
|
||||
this.gotoFolder($item.data('path'))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
MediaManager.prototype.onCommandClick = function(ev) {
|
||||
var command = $(ev.target).data('command')
|
||||
|
||||
switch (command) {
|
||||
case 'refresh' :
|
||||
this.gotoFolder(
|
||||
this.$el.find('[data-type="current-folder"]').val(),
|
||||
true
|
||||
)
|
||||
break;
|
||||
case 'change-view' :
|
||||
this.changeView($(ev.target).data('view'))
|
||||
break;
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
MediaManager.prototype.onItemClick = function(ev) {
|
||||
if (ev.currentTarget.hasAttribute('data-root'))
|
||||
return
|
||||
|
||||
this.selectItem(ev.currentTarget, ev.shiftKey)
|
||||
}
|
||||
|
||||
MediaManager.prototype.onListMouseDown = function(ev) {
|
||||
this.itemListElement.addEventListener('mousemove', this.listMouseMoveHandler)
|
||||
document.addEventListener('mouseup', this.listMouseUpHandler)
|
||||
|
||||
var pagePosition = this.getEventPagePosition(ev),
|
||||
relativePosition = this.getRelativePosition(this.itemListElement, pagePosition.x, pagePosition.y)
|
||||
|
||||
this.selectionStartPoint = relativePosition
|
||||
this.selectionStarted = false
|
||||
}
|
||||
|
||||
MediaManager.prototype.onListMouseUp = function(ev) {
|
||||
this.itemListElement.removeEventListener('mousemove', this.listMouseMoveHandler)
|
||||
document.removeEventListener('mouseup', this.listMouseUpHandler)
|
||||
$(document.body).removeClass('no-select')
|
||||
|
||||
if (this.selectionStarted) {
|
||||
var items = this.itemListElement.querySelectorAll('[data-type="media-item"]:not([data-root])'),
|
||||
selectionPosition = this.getAbsolutePosition(this.selectionMarker)
|
||||
|
||||
for (var index = 0, len = items.length; index < len; index++) {
|
||||
var item = items[index],
|
||||
itemPosition = this.getAbsolutePosition(item)
|
||||
|
||||
if (this.doObjectsCollide(
|
||||
selectionPosition.top,
|
||||
selectionPosition.left,
|
||||
this.selectionMarker.offsetWidth,
|
||||
this.selectionMarker.offsetHeight,
|
||||
itemPosition.top,
|
||||
itemPosition.left,
|
||||
item.offsetWidth,
|
||||
item.offsetHeight)
|
||||
) {
|
||||
if (!ev.shiftKey)
|
||||
item.setAttribute('class', 'selected')
|
||||
else {
|
||||
if (item.getAttribute('class') == 'selected')
|
||||
item.setAttribute('class', '')
|
||||
else
|
||||
item.setAttribute('class', 'selected')
|
||||
}
|
||||
}
|
||||
else if (!ev.shiftKey)
|
||||
item.setAttribute('class', '')
|
||||
}
|
||||
|
||||
this.updateSidebarPreview()
|
||||
this.selectionMarker.setAttribute('class', 'hide')
|
||||
}
|
||||
|
||||
this.selectionStarted = false
|
||||
}
|
||||
|
||||
MediaManager.prototype.onListMouseMove = function(ev) {
|
||||
var pagePosition = this.getEventPagePosition(ev),
|
||||
relativePosition = this.getRelativePosition(this.itemListElement, pagePosition.x, pagePosition.y)
|
||||
|
||||
var deltaX = relativePosition.x - this.selectionStartPoint.x,
|
||||
deltaY = relativePosition.y - this.selectionStartPoint.y
|
||||
|
||||
if (!this.selectionStarted && (Math.abs(deltaX) > 2 || Math.abs(deltaY) > 2)) {
|
||||
// Start processing the selection only if the mouse was moved by
|
||||
// at least 2 pixels.
|
||||
this.createSelectionMarker()
|
||||
|
||||
this.selectionMarker.setAttribute('class', '')
|
||||
this.selectionStarted = true
|
||||
$(document.body).addClass('no-select')
|
||||
}
|
||||
|
||||
if (this.selectionStarted) {
|
||||
if (deltaX >= 0) {
|
||||
this.selectionMarker.style.left = this.selectionStartPoint.x + 'px'
|
||||
this.selectionMarker.style.width = deltaX + 'px'
|
||||
}
|
||||
else {
|
||||
this.selectionMarker.style.left = relativePosition.x + 'px'
|
||||
this.selectionMarker.style.width = Math.abs(deltaX) + 'px'
|
||||
}
|
||||
|
||||
if (deltaY >= 0) {
|
||||
this.selectionMarker.style.height = deltaY + 'px'
|
||||
this.selectionMarker.style.top = this.selectionStartPoint.y + 'px'
|
||||
}
|
||||
else {
|
||||
this.selectionMarker.style.top = relativePosition.y + 'px'
|
||||
this.selectionMarker.style.height = Math.abs(deltaY) + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MEDIA MANAGER PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
MediaManager.DEFAULTS = {
|
||||
alias: ''
|
||||
}
|
||||
|
||||
var old = $.fn.mediaManager
|
||||
|
||||
$.fn.mediaManager = function (option) {
|
||||
var args = Array.prototype.slice.call(arguments, 1),
|
||||
result = undefined
|
||||
|
||||
this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('oc.mediaManager')
|
||||
var options = $.extend({}, MediaManager.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('oc.mediaManager', (data = new MediaManager(this, options)))
|
||||
if (typeof option == 'string') result = data[option].apply(data, args)
|
||||
if (typeof result != 'undefined') return false
|
||||
})
|
||||
|
||||
return result ? result : this
|
||||
}
|
||||
|
||||
$.fn.mediaManager.Constructor = MediaManager
|
||||
|
||||
// MEDIA MANAGER NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.mediaManager.noConflict = function () {
|
||||
$.fn.mediaManager = old
|
||||
return this
|
||||
}
|
||||
|
||||
// MEDIA MANAGER DATA-API
|
||||
// ===============
|
||||
|
||||
$(document).on('render', function(){
|
||||
$('div[data-control=media-manager]').mediaManager()
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
@import "../../../../../backend/assets/less/core/boot.less";
|
||||
|
||||
.media-selected-tiles() {
|
||||
.icon-container {
|
||||
background: @color-list-hover-bg!important;
|
||||
border-color: #2581b8;
|
||||
|
||||
i, p {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #2581b8;
|
||||
}
|
||||
}
|
||||
|
||||
.media-selected-list() {
|
||||
background: @color-list-hover-bg!important;
|
||||
|
||||
i, p.size {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
border-right-color: @color-list-hover-bg!important;
|
||||
}
|
||||
}
|
||||
|
||||
div[data-control="media-manager"] {
|
||||
.loading-indicator-pseudo-absolute(@size) {
|
||||
background-image: url(../../../../../../modules/backend/assets/images/loading-indicator-transparent.svg);
|
||||
background-position: 50% 50%;
|
||||
content: ' ';
|
||||
.animation(spin 1s linear infinite);
|
||||
|
||||
background-size: @size @size;
|
||||
position: absolute;
|
||||
width: @size;
|
||||
height: @size;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -@size/2;
|
||||
margin-left: -@size/2;
|
||||
}
|
||||
|
||||
audio, video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
video {
|
||||
background: #ecf0f1;
|
||||
max-height: 225px;
|
||||
}
|
||||
|
||||
.media-player-fallback {
|
||||
font-size: 13px;
|
||||
color: #95a5a6;
|
||||
background: #ecf0f1;
|
||||
line-height: 180%;
|
||||
|
||||
&.panel-embedded {
|
||||
padding: 20px;
|
||||
margin: -20px -20px 0 -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-message() {
|
||||
font-size: 12px;
|
||||
margin-top: 25px;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
|
||||
p.thumbnail-error-message {
|
||||
.icon-message();
|
||||
}
|
||||
|
||||
.media-list {
|
||||
padding: 0 0 20px 20px;
|
||||
.user-select(none);
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 0 20px 20px 0;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
.border-radius(3px);
|
||||
|
||||
.icon-container {
|
||||
display: table;
|
||||
|
||||
i {
|
||||
color: #95a5a6;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-container.image {
|
||||
> div.icon-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #2b3e50;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 15px;
|
||||
line-height: 150%;
|
||||
margin: 15px 0 5px 0;
|
||||
}
|
||||
|
||||
p.size {
|
||||
font-size: 12px;
|
||||
color: #95a5a6;
|
||||
}
|
||||
|
||||
.image-placeholder {
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
padding-top: 0;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
&[data-loading] {
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-loading]:after {
|
||||
.loading-indicator-pseudo-absolute(28px);
|
||||
}
|
||||
}
|
||||
|
||||
i.icon-chain-broken {
|
||||
padding: 0;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
|
||||
&[data-item-type=folder] i {
|
||||
color: @color-list-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.list {
|
||||
li {
|
||||
height: 75px;
|
||||
width: 260px;
|
||||
border: 1px solid #ecf0f1;
|
||||
background: #f6f8f9;
|
||||
}
|
||||
|
||||
li .icon-container {
|
||||
border-right: 1px solid #f6f8f9;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
float: left;
|
||||
|
||||
i {
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
&.image {
|
||||
border-right: 1px solid #ecf0f1!important;
|
||||
}
|
||||
|
||||
p.thumbnail-error-message {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
li .info {
|
||||
margin-left: 90px;
|
||||
}
|
||||
|
||||
li .image-placeholder {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
li[data-root] h4 {
|
||||
margin-top: 27px;
|
||||
}
|
||||
|
||||
li.selected {
|
||||
.media-selected-list();
|
||||
}
|
||||
}
|
||||
|
||||
&.tiles {
|
||||
li {
|
||||
width: 167px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 167px;
|
||||
}
|
||||
|
||||
li .image-placeholder {
|
||||
width: 165px;
|
||||
height: 165px;
|
||||
|
||||
&[data-loading]:after {
|
||||
.loading-indicator-pseudo-absolute(55px);
|
||||
}
|
||||
}
|
||||
|
||||
li .icon-container {
|
||||
width: 167px;
|
||||
height: 167px;
|
||||
.border-radius(3px);
|
||||
border: 1px solid #ecf0f1;
|
||||
overflow: hidden;
|
||||
background: #f6f8f9;
|
||||
|
||||
i {
|
||||
font-size: 55px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: @font-family-sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
li.selected {
|
||||
.media-selected-tiles();
|
||||
}
|
||||
|
||||
i.icon-chain-broken {
|
||||
margin-top: 47px;
|
||||
}
|
||||
|
||||
p.size {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-image-placeholder-container {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-image-placeholder {
|
||||
display: table-cell;
|
||||
height: 225px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
|
||||
&[data-loading] {
|
||||
background: #ecf0f1;
|
||||
&:after {
|
||||
.loading-indicator-pseudo-absolute(62px);
|
||||
}
|
||||
}
|
||||
|
||||
i.icon-chain-broken, i.icon-crop, i.icon-asterisk {
|
||||
color: #bdc3c7;
|
||||
font-size: 55px;
|
||||
}
|
||||
|
||||
&.no-border {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
p {
|
||||
.icon-message();
|
||||
}
|
||||
}
|
||||
|
||||
.list-container {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
[data-control="item-list"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div[data-control="selection-marker"] {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
border: 1px dashed #95a5a6;
|
||||
}
|
||||
}
|
||||
|
||||
body:not(.no-select) {
|
||||
div[data-control="media-manager"] .media-list {
|
||||
&.tiles {
|
||||
li:hover {
|
||||
.media-selected-tiles();
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
li:hover {
|
||||
.media-selected-list();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<div data-control="media-manager" class="layout" data-alias="<?= $this->alias ?>">
|
||||
<?= $this->makePartial('toolbar') ?>
|
||||
|
||||
<div class="layout-row whiteboard">
|
||||
<div class="layout">
|
||||
<div class="layout-row">
|
||||
<div class="layout-cell width-200 panel border-right">
|
||||
<?= $this->makePartial('left-sidebar') ?>
|
||||
</div>
|
||||
<div class="layout-cell">
|
||||
<div class="layout">
|
||||
|
||||
<div class="layout-row min-size">
|
||||
<?= $this->makePartial('folder-toolbar') ?>
|
||||
</div>
|
||||
<div class="layout-row">
|
||||
<!-- Main area -->
|
||||
<div class="layout">
|
||||
<div class="layout-cell ">
|
||||
<div class="layout">
|
||||
<div class="layout-row">
|
||||
<!-- Main area - list -->
|
||||
<div data-control="item-list">
|
||||
<div id="<?= $this->getId('item-list') ?>" >
|
||||
<?= $this->makePartial('item-list') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-row min-size">
|
||||
<!-- Main area - bottom toolbar -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-cell width-300 panel border-left no-padding" data-control="preview-sidebar">
|
||||
<!-- Right sidebar -->
|
||||
<?= $this->makePartial('right-sidebar') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<ul class="tree-path">
|
||||
<li class="root"><a href="#" data-type="media-item" data-item-type="folder" data-path="/"><?= e(trans('cms::lang.media.library')) ?></a></li>
|
||||
|
||||
<?php foreach ($pathSegments as $segment): ?>
|
||||
<?php if ($segment != '/'): ?>
|
||||
<li><a href="#" data-type="media-item" data-item-type="folder" data-path="<?= e($segment) ?>"><?= basename($segment) ?></a></li>
|
||||
<?php endif ?>
|
||||
<?php endforeach?>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<div class="panel padding-less border-bottom triangle-down">
|
||||
<div class="layout">
|
||||
<div class="layout-cell">
|
||||
<div class="layout-row" id="<?= $this->getId('folder-path') ?>">
|
||||
<?= $this->makePartial('folder-path') ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-cell">
|
||||
<button
|
||||
type="button"
|
||||
class="oc-icon-sign-out btn-icon pull-right larger">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<ul class="media-list <?= $listClass ?>">
|
||||
<?php if (count($items) > 0 || !$isRootFolder): ?>
|
||||
<?php if (!$isRootFolder): ?>
|
||||
<li data-type="media-item" data-item-type="folder" data-root data-path="<?= e(dirname($currentFolder)) ?>">
|
||||
<div class="icon-container folder">
|
||||
<div class="icon-wrapper"><i class="icon-folder"></i></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h4 title="<?= e(trans('cms::lang.media.return_to_parent')) ?>"><?= e(trans('cms::lang.media.return_to_parent_label')) ?></h4>
|
||||
</div>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php foreach ($items as $item):
|
||||
$itemType = $item->getFileType();
|
||||
?>
|
||||
<li data-type="media-item"
|
||||
data-item-type="<?= $item->type ?>"
|
||||
data-path="<?= e($item->path) ?>"
|
||||
data-title="<?= e(basename($item->path)) ?>"
|
||||
data-size="<?= e($item->sizeToString()) ?>"
|
||||
data-last-modified="<?= e($item->lastModifiedAsString()) ?>"
|
||||
data-last-modified-ts="<?= $item->lastModified ?>"
|
||||
data-public-url="<?= e($item->publicUrl) ?>"
|
||||
data-document-type="<?= e($itemType) ?>"
|
||||
>
|
||||
<?= $this->makePartial('item-icon', ['itemType'=>$itemType, 'item'=>$item]) ?>
|
||||
|
||||
<div class="info">
|
||||
<h4 title="<?= e(basename($item->path)) ?>"><?= e(basename($item->path)) ?></h4>
|
||||
<p class="size"><?= e($item->sizeToString()) ?></p>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<div class="icon-container <?= $itemType ?>">
|
||||
<div class="icon-wrapper"><i class="<?= $this->itemTypeToIconClass($item, $itemType) ?>"></i></div>
|
||||
|
||||
<?php if ($itemType == Cms\Classes\MediaLibraryItem::FILE_TYPE_IMAGE):
|
||||
$thumbnailPath = $this->thumbnailExists($thumbnailParams, $item->path, $item->lastModified);
|
||||
?>
|
||||
<div>
|
||||
<?php if (!$thumbnailPath): ?>
|
||||
<div class="image-placeholder"
|
||||
data-width="<?= $thumbnailParams['width'] ?>"
|
||||
data-height="<?= $thumbnailParams['height'] ?>"
|
||||
data-path="<?= e($item->path) ?>"
|
||||
data-last-modified="<?= $item->lastModified ?>"
|
||||
id="<?= $this->getPlaceholderId($item) ?>"
|
||||
>
|
||||
<div class="icon-wrapper"><i class="<?= $this->itemTypeToIconClass($item, $itemType) ?>"></i></div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?= $this->makePartial('thumbnail-image', [
|
||||
'isError' => $this->thumbnailIsError($thumbnailPath),
|
||||
'imageUrl' => $this->getThumbnailImageUrl($thumbnailPath)
|
||||
]) ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<div class="panel no-padding padding-top">
|
||||
<input type="hidden" data-type="current-folder" value="<?= e($currentFolder) ?>"/>
|
||||
<div class="list-container">
|
||||
<?php if ($viewMode == Cms\Widgets\MediaManager::VIEW_MODE_GRID): ?>
|
||||
<?= $this->makePartial('list-grid') ?>
|
||||
<?php elseif ($viewMode == Cms\Widgets\MediaManager::VIEW_MODE_LIST): ?>
|
||||
<?= $this->makePartial('list-list') ?>
|
||||
<?php else: ?>
|
||||
<?= $this->makePartial('list-tiles') ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<div data-control="media-preview-container"></div>
|
||||
|
||||
<script type="text/template" data-control="audio-template">
|
||||
<div class="panel no-padding-bottom">
|
||||
<audio src="{src}" controls>
|
||||
<div class="media-player-fallback panel-embedded">Your browser doesn't support HTML5 audio.</div>
|
||||
</audio>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" data-control="video-template">
|
||||
<video src="{src}" controls poster="<?= URL::to('modules/cms/Widgets/mediamanager/assets/images/video-poster.png') ?>">
|
||||
<div class="panel media-player-fallback">Your browser doesn't support HTML5 video.</div>
|
||||
</video>
|
||||
</script>
|
||||
|
||||
<script type="text/template" data-control="image-template">
|
||||
<div class="sidebar-image-placeholder-container"><div class="sidebar-image-placeholder" data-path="{path}" data-last-modified="{last-modified}" data-loading="true" data-control="sidebar-thumbnail"></div></div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" data-control="no-selection-template">
|
||||
<div class="sidebar-image-placeholder-container">
|
||||
<div class="sidebar-image-placeholder no-border">
|
||||
<i class="icon-crop"></i>
|
||||
<p><?= e(trans('cms::lang.media.nothing_selected')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" data-control="multi-selection-template">
|
||||
<div class="sidebar-image-placeholder-container">
|
||||
<div class="sidebar-image-placeholder no-border">
|
||||
<i class="icon-asterisk"></i>
|
||||
<p><?= e(trans('cms::lang.media.multiple_selected')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<h3 class="section">Display</h3>
|
||||
|
||||
<ul class="nav nav-stacked selector-group">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#">
|
||||
<i class="icon-recycle"></i>
|
||||
|
||||
<?= e(trans('cms::lang.media.filter_everything')) ?>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#">
|
||||
<i class="icon-picture-o"></i>
|
||||
|
||||
<?= e(trans('cms::lang.media.filter_images')) ?>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#">
|
||||
<i class="icon-video-camera"></i>
|
||||
|
||||
<?= e(trans('cms::lang.media.filter_video')) ?>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#">
|
||||
<i class="icon-volume-up"></i>
|
||||
|
||||
<?= e(trans('cms::lang.media.filter_audio')) ?>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#">
|
||||
<i class="icon-file"></i>
|
||||
|
||||
<?= e(trans('cms::lang.media.filter_documents')) ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<table class="table data">
|
||||
<tbody class="icons clickable">
|
||||
<?php if (count($items) > 0 || !$isRootFolder): ?>
|
||||
<?php if (!$isRootFolder): ?>
|
||||
<tr data-type="media-item" data-item-type="folder" data-root data-path="<?= e(dirname($currentFolder)) ?>">
|
||||
<td><i class="icon-folder"></i>..</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php foreach ($items as $item):
|
||||
$itemType = $item->getFileType();
|
||||
?>
|
||||
<tr data-type="media-item"
|
||||
data-item-type="<?= $item->type ?>"
|
||||
data-path="<?= e($item->path) ?>"
|
||||
data-title="<?= e(basename($item->path)) ?>"
|
||||
data-size="<?= e($item->sizeToString()) ?>"
|
||||
data-last-modified="<?= e($item->lastModifiedAsString()) ?>"
|
||||
data-last-modified-ts="<?= $item->lastModified ?>"
|
||||
data-public-url="<?= e($item->publicUrl) ?>"
|
||||
data-document-type="<?= e($itemType) ?>"
|
||||
>
|
||||
<td><i class="<?= $this->itemTypeToIconClass($item, $itemType) ?>"></i> <?= e(basename($item->path)) ?></td>
|
||||
<td><?= e($item->sizeToString()) ?></td>
|
||||
<td><?= e($item->lastModifiedAsString()) ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?= $this->makePartial('generic-list', ['listClass'=>'list']) ?>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?= $this->makePartial('generic-list', ['listClass'=>'tiles']) ?>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?= $this->makePartial('item-sidebar-preview') ?>
|
||||
|
||||
<div class="panel hide" data-control="sidebar-labels">
|
||||
<label><?= e(trans('cms::lang.media.title')) ?></label>
|
||||
<p data-label="title"></p>
|
||||
|
||||
<table class="name-value-list">
|
||||
<tr>
|
||||
<th><?= e(trans('cms::lang.media.size')) ?></th>
|
||||
<td data-label="size"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= e(trans('cms::lang.media.public_url')) ?></th>
|
||||
<td><a href="#" data-label="public-url" target="_blank"><?= e(trans('cms::lang.media.click_here')) ?></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= e(trans('cms::lang.media.last_modified')) ?></th>
|
||||
<td data-label="last-modified"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php if (!$isError): ?>
|
||||
<img src="<?= $imageUrl ?>"/>
|
||||
<?php else: ?>
|
||||
<i class="icon-chain-broken" title="<?= e(trans('cms::lang.media.thumbnail_error')) ?>"></i>
|
||||
<p class="thumbnail-error-message"><?= e(trans('cms::lang.media.thumbnail_error')) ?></p>
|
||||
<?php endif ?>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<div class="layout-row min-size">
|
||||
<div class="layout control-toolbar standalone-paddings">
|
||||
<div class="layout-cell">
|
||||
<div class="btn-group offset-right">
|
||||
<button type="button" class="btn btn-primary oc-icon-upload"
|
||||
><?= e(trans('cms::lang.media.upload')) ?></button>
|
||||
<button type="button" class="btn btn-primary oc-icon-folder"
|
||||
><?= e(trans('cms::lang.media.add_folder')) ?></button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-default oc-icon-refresh empty offset-right" data-command="refresh"></button>
|
||||
|
||||
<div class="btn-group offset-right" id="<?= $this->getId('view-mode-buttons') ?>">
|
||||
<?= $this->makePartial('view-mode-buttons') ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-cell width-fix">
|
||||
<div class="relative toolbar-item loading-indicator-container size-input-text last">
|
||||
<input placeholder="<?= e(trans('cms::lang.media.search')) ?>" type="text" name="search" value=""
|
||||
class="form-control icon search" autocomplete="off"
|
||||
data-track-input
|
||||
data-load-indicator
|
||||
data-load-indicator-opaque
|
||||
data-request="<?= $this->getEventHandler('onSearch') ?>"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<button
|
||||
type="button"
|
||||
class="btn btn-default oc-icon-align-justify empty <?= $viewMode == Cms\Widgets\MediaManager::VIEW_MODE_GRID ? 'on' : '' ?>"
|
||||
data-command="change-view"
|
||||
data-view="<?= Cms\Widgets\MediaManager::VIEW_MODE_GRID ?>">
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default oc-icon-th empty <?= $viewMode == Cms\Widgets\MediaManager::VIEW_MODE_LIST ? 'on' : '' ?>"
|
||||
data-command="change-view"
|
||||
data-view="<?= Cms\Widgets\MediaManager::VIEW_MODE_LIST ?>">
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default oc-icon-th-large empty <?= $viewMode == Cms\Widgets\MediaManager::VIEW_MODE_TILES ? 'on' : '' ?>"
|
||||
data-command="change-view"
|
||||
data-view="<?= Cms\Widgets\MediaManager::VIEW_MODE_TILES ?>">
|
||||
</button>
|
||||
|
|
@ -120,7 +120,7 @@ if (window.jQuery === undefined)
|
|||
var errorMsg,
|
||||
updatePromise = $.Deferred()
|
||||
|
||||
if (isUnloading)
|
||||
if (isUnloading || errorThrown == 'abort')
|
||||
return
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in New Issue