feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run

- Base: wintercms/winter branch 1.2 (full framework)
- Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS
- Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication
- Partials: hero (offline-first), features, modes (offline/nube toggle),
  screenshots, pricing (3 planes), comparison, FAQ, CTA
- Plugin VivesPOS.Site with ContactForm
- Dockerfile: PHP 8.2 Apache, port 80, healthcheck
- Added winter/wn-pages, blog, sitemap, seo plugins
- Active theme set to vivespos
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,513 @@
<?php namespace Backend\Widgets;
use File;
use Lang;
use Flash;
use Request;
use BackendAuth;
use Backend\Classes\WidgetBase;
use Backend\Classes\WidgetManager;
use Backend\Models\UserPreference;
use System\Models\Parameter as SystemParameters;
use ApplicationException;
/**
* Report Container Widget
* Creates an area hosting report widgets.
*
* @package winter\wn-backend-module
* @author Alexey Bobkov, Samuel Georges
*/
class ReportContainer extends WidgetBase
{
//
// Configurable properties
//
/**
* @var string The unique report context name
* Defines the context where the container is used.
* Widget settings are saved in a specific context. This allows to
* have multiple report containers on different pages that have
* different widgets and widget settings. Context names can contain
* only Latin letters.
*/
public $context = 'dashboard';
/**
* @var string Determines whether widgets could be added and deleted.
*/
public $canAddAndDelete = true;
/**
* @var array A list of default widgets to load.
* This structure could be defined in the widget configuration file (for example config_report_container.yaml).
* Example YAML structure:
*
* defaultWidgets:
* trafficOverview:
* class: Winter\GoogleAnalytics\ReportWidgets\TrafficOverview
* sortOrder: 1
* configuration:
* title: 'Traffic overview'
* ocWidgetWidth: 12
*/
public $defaultWidgets = [];
//
// Object properties
//
/**
* @inheritDoc
*/
protected $defaultAlias = 'reportContainer';
/**
* @var array Collection of all report widgets used by this container.
*/
protected $reportWidgets = [];
/**
* @var boolean Determines if report widgets have been created.
*/
protected $reportsDefined = false;
/**
* Constructor.
*/
public function __construct($controller, $configuration = null)
{
if (!$configuration) {
$configuration = 'config_report_container.yaml';
}
if (is_string($configuration)) {
$path = $controller->getConfigPath($configuration);
if (File::isFile($path)) {
$configuration = $this->makeConfig($path);
}
else {
$configuration = [];
}
}
parent::__construct($controller, $configuration);
$this->fillFromConfig();
$this->bindToController();
}
/**
* Ensure report widgets are registered so they can also be bound to
* the controller this allows their AJAX features to operate.
* @return void
*/
public function bindToController()
{
$this->defineReportWidgets();
parent::bindToController();
}
/**
* Renders this widget along with its collection of report widgets.
*/
public function render()
{
$this->defineReportWidgets();
$this->vars['widgets'] = $this->reportWidgets;
return $this->makePartial('container');
}
/**
* @inheritDoc
*/
protected function loadAssets()
{
$this->addCss('css/reportcontainer.css', 'core');
$this->addJs('vendor/isotope/jquery.isotope.min.js', 'core');
$this->addJs('js/reportcontainer.js', 'core');
}
//
// Event handlers
//
public function onResetWidgets()
{
$this->resetWidgets();
$this->vars['widgets'] = $this->reportWidgets;
Flash::success(Lang::get('backend::lang.dashboard.reset_layout_success'));
return ['#'.$this->getId('container-list') => $this->makePartial('widget_list')];
}
public function onMakeLayoutDefault()
{
if (!BackendAuth::getUser()->hasAccess('backend.manage_default_dashboard')) {
throw new ApplicationException("You do not have permission to do that.");
}
$widgets = $this->getWidgetsFromUserPreferences();
SystemParameters::set($this->getSystemParametersKey(), $widgets);
Flash::success(Lang::get('backend::lang.dashboard.make_default_success'));
}
public function onUpdateWidget()
{
$alias = Request::input('alias');
$widget = $this->findWidgetByAlias($alias);
$widget->setProperties(json_decode(Request::input('fields'), true));
$this->saveWidgetProperties($alias, $widget->getProperties());
return [
'#'.$alias => $widget->render()
];
}
public function onRemoveWidget()
{
$alias = Request::input('alias');
$this->removeWidget($alias);
}
public function onLoadAddPopup()
{
$sizes = [];
for ($i = 1; $i <= 12; $i++) {
$sizes[$i] = $i < 12 ? $i : $i.' (' . Lang::get('backend::lang.dashboard.full_width') . ')';
}
$this->vars['sizes'] = $sizes;
$this->vars['widgets'] = WidgetManager::instance()->listReportWidgets();
return $this->makePartial('new_widget_popup');
}
public function onAddWidget()
{
$className = trim(Request::input('className'));
$size = trim(Request::input('size'));
if (!$className) {
throw new ApplicationException('Please select a widget to add.');
}
if (!class_exists($className)) {
throw new ApplicationException('The selected class doesn\'t exist.');
}
$widget = new $className($this->controller);
if (!($widget instanceof \Backend\Classes\ReportWidgetBase)) {
throw new ApplicationException('The selected class is not a report widget.');
}
$widgetInfo = $this->addWidget($widget, $size);
return [
'@#'.$this->getId('container-list') => $this->makePartial('widget', [
'widget' => $widget,
'widgetAlias' => $widgetInfo['alias'],
'sortOrder' => $widgetInfo['sortOrder']
])
];
}
public function addWidget($widget, $size)
{
if (!$this->canAddAndDelete) {
throw new ApplicationException('Access denied.');
}
$widgets = $this->getWidgetsFromUserPreferences();
$num = count($widgets);
do {
$num++;
$alias = 'report_container_'.$this->context.'_'.$num;
}
while (array_key_exists($alias, $widgets));
// Ensure that the widget's alias is correctly set for this request
$widget->alias = $alias;
$sortOrder = 0;
foreach ($widgets as $widgetInfo) {
$sortOrder = max($sortOrder, $widgetInfo['sortOrder']);
}
$sortOrder++;
$widget->setProperty('ocWidgetWidth', $size);
$widgets[$alias] = [
'class' => get_class($widget),
'configuration' => $widget->getProperties(),
'sortOrder' => $sortOrder
];
$this->setWidgetsToUserPreferences($widgets);
return [
'alias' => $alias,
'sortOrder' => $widgets[$alias]['sortOrder']
];
}
public function onSetWidgetOrders()
{
$aliases = trim(Request::input('aliases'));
$orders = trim(Request::input('orders'));
if (!$aliases) {
throw new ApplicationException('Invalid aliases string.');
}
if (!$orders) {
throw new ApplicationException('Invalid orders string.');
}
$aliases = explode(',', $aliases);
$orders = explode(',', $orders);
if (count($aliases) != count($orders)) {
throw new ApplicationException('Invalid data posted.');
}
$widgets = $this->getWidgetsFromUserPreferences();
foreach ($aliases as $index => $alias) {
if (isset($widgets[$alias])) {
$widgets[$alias]['sortOrder'] = $orders[$index];
}
}
$this->setWidgetsToUserPreferences($widgets);
}
//
// Methods for internal use
//
/**
* Registers the report widgets that will be included in this container.
* The chosen widgets are based on the user preferences.
*/
protected function defineReportWidgets()
{
if ($this->reportsDefined) {
return;
}
$result = [];
$widgets = $this->getWidgetsFromUserPreferences();
foreach ($widgets as $alias => $widgetInfo) {
if ($widget = $this->makeReportWidget($alias, $widgetInfo)) {
$result[$alias] = $widget;
}
}
uasort($result, function ($a, $b) {
return $a['sortOrder'] - $b['sortOrder'];
});
$this->reportWidgets = $result;
$this->reportsDefined = true;
}
/**
* Makes a single report widget object, returned array index:
* - widget: The widget object (Backend\Classes\ReportWidgetBase)
* - sortOrder: The current sort order
*
* @param string $alias
* @param array $widgetInfo
* @return array
*/
protected function makeReportWidget($alias, $widgetInfo)
{
$configuration = $widgetInfo['configuration'];
$configuration['alias'] = $alias;
$className = $widgetInfo['class'];
$availableReportWidgets = array_keys(WidgetManager::instance()->listReportWidgets());
if (!class_exists($className) || !in_array($className, $availableReportWidgets)) {
return;
}
$widget = new $className($this->controller, $configuration);
$widget->bindToController();
return ['widget' => $widget, 'sortOrder' => $widgetInfo['sortOrder']];
}
protected function resetWidgets()
{
$this->resetWidgetsUserPreferences();
$this->reportsDefined = false;
$this->defineReportWidgets();
}
protected function removeWidget($alias)
{
if (!$this->canAddAndDelete) {
throw new ApplicationException('Access denied.');
}
$widgets = $this->getWidgetsFromUserPreferences();
if (isset($widgets[$alias])) {
unset($widgets[$alias]);
}
$this->setWidgetsToUserPreferences($widgets);
}
protected function findWidgetByAlias($alias)
{
$this->defineReportWidgets();
$widgets = $this->reportWidgets;
if (!isset($widgets[$alias])) {
throw new ApplicationException('The specified widget is not found.');
}
return $widgets[$alias]['widget'];
}
protected function getWidgetPropertyConfig($widget)
{
$properties = $widget->defineProperties();
$property = [
'property' => 'ocWidgetWidth',
'title' => Lang::get('backend::lang.dashboard.widget_columns_label', ['columns' => '(1-12)']),
'description' => Lang::get('backend::lang.dashboard.widget_columns_description'),
'type' => 'dropdown',
'validationPattern' => '^[0-9]+$',
'validationMessage' => Lang::get('backend::lang.dashboard.widget_columns_error'),
'options' => [
1 => '1 ' . Lang::choice('backend::lang.dashboard.columns', 1),
2 => '2 ' . Lang::choice('backend::lang.dashboard.columns', 2),
3 => '3 ' . Lang::choice('backend::lang.dashboard.columns', 3),
4 => '4 ' . Lang::choice('backend::lang.dashboard.columns', 4),
5 => '5 ' . Lang::choice('backend::lang.dashboard.columns', 5),
6 => '6 ' . Lang::choice('backend::lang.dashboard.columns', 6),
7 => '7 ' . Lang::choice('backend::lang.dashboard.columns', 7),
8 => '8 ' . Lang::choice('backend::lang.dashboard.columns', 8),
9 => '9 ' . Lang::choice('backend::lang.dashboard.columns', 9),
10 => '10 ' . Lang::choice('backend::lang.dashboard.columns', 10),
11 => '11 ' . Lang::choice('backend::lang.dashboard.columns', 11),
12 => '12 ' . Lang::choice('backend::lang.dashboard.columns', 12)
]
];
$result[] = $property;
$property = [
'property' => 'ocWidgetNewRow',
'title' => Lang::get('backend::lang.dashboard.widget_new_row_label'),
'description' => Lang::get('backend::lang.dashboard.widget_new_row_description'),
'type' => 'checkbox'
];
$result[] = $property;
foreach ($properties as $name => $params) {
$property = [
'property' => $name,
'title' => isset($params['title']) ? Lang::get($params['title']) : $name,
'type' => $params['type'] ?? 'string'
];
foreach ($params as $name => $value) {
if (isset($property[$name])) {
continue;
}
$property[$name] = !is_array($value) ? Lang::get($value) : $value;
}
$result[] = $property;
}
return json_encode($result);
}
protected function getWidgetPropertyValues($widget)
{
$result = [];
$properties = $widget->defineProperties();
foreach ($properties as $name => $params) {
$value = $widget->property($name);
if (is_string($value)) {
$value = Lang::get($value);
}
$result[$name] = $value;
}
$result['ocWidgetWidth'] = $widget->property('ocWidgetWidth');
$result['ocWidgetNewRow'] = $widget->property('ocWidgetNewRow');
return json_encode($result);
}
//
// User and system value storage
//
protected function getWidgetsFromUserPreferences()
{
$defaultWidgets = SystemParameters::get($this->getSystemParametersKey(), $this->defaultWidgets);
$widgets = UserPreference::forUser()
->get($this->getUserPreferencesKey(), $defaultWidgets);
if (!is_array($widgets)) {
return [];
}
return $widgets;
}
protected function setWidgetsToUserPreferences($widgets)
{
UserPreference::forUser()->set($this->getUserPreferencesKey(), $widgets);
}
protected function resetWidgetsUserPreferences()
{
UserPreference::forUser()->reset($this->getUserPreferencesKey());
}
protected function saveWidgetProperties($alias, $properties)
{
$widgets = $this->getWidgetsFromUserPreferences();
if (isset($widgets[$alias])) {
$widgets[$alias]['configuration'] = $properties;
$this->setWidgetsToUserPreferences($widgets);
}
}
protected function getUserPreferencesKey()
{
return 'backend::reportwidgets.'.$this->context;
}
protected function getSystemParametersKey()
{
return 'backend::reportwidgets.default.'.$this->context;
}
}

View File

@@ -0,0 +1,174 @@
<?php namespace Backend\Widgets;
use Lang;
use Backend\Classes\WidgetBase;
/**
* Search Widget
* Used for building a toolbar, Renders a search container.
*
* @package winter\wn-backend-module
* @author Alexey Bobkov, Samuel Georges
*/
class Search extends WidgetBase
{
//
// Configurable properties
//
/**
* @var string Search placeholder text.
*/
public $prompt;
/**
* @var bool Field show grow when selected.
*/
public $growable = true;
/**
* @var string Custom partial file definition, in context of the controller.
*/
public $partial;
/**
* @var string Defines the search mode. Commonly passed to the searchWhere() query.
*/
public $mode;
/**
* @var string Custom scope method name. Commonly passed to the query.
*/
public $scope;
/**
* @var bool Search on enter key instead of every key stroke.
*/
public $searchOnEnter = false;
//
// Object properties
//
/**
* @inheritDoc
*/
protected $defaultAlias = 'search';
/**
* @var string Active search term pulled from session data.
*/
protected $activeTerm;
/**
* @var array List of CSS classes to apply to the list container element.
*/
public $cssClasses = [];
/**
* Initialize the widget, called by the constructor and free from its parameters.
*/
public function init()
{
$this->fillFromConfig([
'prompt',
'partial',
'growable',
'scope',
'mode',
'searchOnEnter',
]);
/*
* Add CSS class styles
*/
$this->cssClasses[] = 'icon search';
if ($this->growable) {
$this->cssClasses[] = 'growable';
}
}
/**
* Renders the widget.
*/
public function render()
{
$this->prepareVars();
if ($this->partial) {
return $this->controller->makePartial($this->partial);
}
return $this->makePartial('search');
}
/**
* Prepares the view data
*/
public function prepareVars()
{
$this->vars['cssClasses'] = implode(' ', $this->cssClasses);
$this->vars['placeholder'] = Lang::get($this->prompt);
$this->vars['value'] = $this->getActiveTerm();
$this->vars['searchOnEnter'] = $this->searchOnEnter;
}
/**
* Search field has been submitted.
*/
public function onSubmit()
{
/*
* Save or reset search term in session
*/
$this->setActiveTerm(post($this->getName()));
/*
* Trigger class event, merge results as viewable array
*/
$params = func_get_args();
try {
$result = $this->fireEvent('search.submit', [$params]);
} catch (\Throwable $e) {
// Remove the search term from the session if the search has failed.
$this->setActiveTerm('');
throw $e;
}
if ($result && is_array($result)) {
return call_user_func_array('array_merge', $result);
}
}
/**
* Returns an active search term for this widget instance.
*/
public function getActiveTerm()
{
return $this->activeTerm = $this->getSession('term', '');
}
/**
* Sets an active search term for this widget instance.
*/
public function setActiveTerm($term)
{
if (strlen($term)) {
$this->putSession('term', $term);
} else {
$this->resetSession();
}
$this->activeTerm = $term;
}
/**
* Returns a value suitable for the field name property.
* @return string
*/
public function getName()
{
return $this->alias . '[term]';
}
}

View File

@@ -0,0 +1,322 @@
<?php namespace Backend\Widgets;
use Config;
use Backend;
use Lang;
use Input;
use Request;
use Backend\Classes\WidgetBase;
use Winter\Storm\Html\Helper as HtmlHelper;
use SystemException;
/**
* Table Widget.
*
* Represents an editable tabular control.
*
* @package winter\wn-backend-module
* @author Alexey Bobkov, Samuel Georges
*/
class Table extends WidgetBase
{
/**
* @inheritDoc
*/
protected $defaultAlias = 'table';
/**
* @var array Table columns
*/
protected $columns = [];
/**
* @var boolean Show data table header
*/
protected $showHeader = true;
/**
* @var Backend\Widgets\Table\DatasourceBase
*/
protected $dataSource;
/**
* @var string Field name used for request data.
*/
protected $fieldName;
/**
* @var string
*/
protected $recordsKeyFrom;
protected $dataSourceAliases = [
'client' => '\Backend\Widgets\Table\ClientMemoryDataSource',
'server' => '\Backend\Widgets\Table\ServerEventDataSource'
];
/**
* Initialize the widget, called by the constructor and free from its parameters.
*/
public function init()
{
$this->columns = $this->getConfig('columns', []);
$this->fieldName = $this->getConfig('fieldName', $this->alias);
$this->recordsKeyFrom = $this->getConfig('keyFrom', 'id');
$dataSourceClass = $this->getConfig('dataSource');
if (!strlen($dataSourceClass)) {
throw new SystemException('The Table widget data source is not specified in the configuration.');
}
if (array_key_exists($dataSourceClass, $this->dataSourceAliases)) {
$dataSourceClass = $this->dataSourceAliases[$dataSourceClass];
}
if (!class_exists($dataSourceClass)) {
throw new SystemException(sprintf('The Table widget data source class "%s" is could not be found.', $dataSourceClass));
}
$this->dataSource = new $dataSourceClass($this->recordsKeyFrom);
if (Request::method() == 'POST' && $this->isClientDataSource()) {
// Use dot notation for request data field
$requestDataField = implode('.', HtmlHelper::nameToArray($this->fieldName));
if (Request::exists($requestDataField)) {
// Load data into the client memory data source on POST
$this->dataSource->purge();
$this->dataSource->initRecords(Request::input($requestDataField));
}
}
}
/**
* Returns the data source object.
* @return \Backend\Widgets\Table\DataSourceBase
*/
public function getDataSource()
{
return $this->dataSource;
}
/**
* Renders the widget.
*/
public function render()
{
$this->prepareVars();
return $this->makePartial('table');
}
/**
* Prepares the view data
*/
public function prepareVars()
{
$this->vars['columns'] = $this->prepareColumnsArray();
$this->vars['recordsKeyFrom'] = $this->recordsKeyFrom;
$this->vars['recordsPerPage'] = $this->getConfig('recordsPerPage', false) ?: 'false';
$this->vars['postbackHandlerName'] = $this->getConfig('postbackHandlerName');
$this->vars['searching'] = $this->getConfig('searching', false);
$this->vars['adding'] = $this->getConfig('adding', true);
$this->vars['deleting'] = $this->getConfig('deleting', true);
$this->vars['toolbar'] = $this->getConfig('toolbar', true);
$this->vars['height'] = $this->getConfig('height', false) ?: 'false';
$this->vars['dynamicHeight'] = $this->getConfig('dynamicHeight', false) ?: 'false';
$this->vars['btnAddRowLabel'] = Lang::get($this->getConfig('btnAddRowLabel', 'backend::lang.form.insert_row'));
$this->vars['btnAddRowBelowLabel'] = Lang::get($this->getConfig('btnAddRowBelowLabel', 'backend::lang.form.insert_row_below'));
$this->vars['btnDeleteRowLabel'] = Lang::get($this->getConfig('btnDeleteRowLabel', 'backend::lang.form.delete_row'));
$isClientDataSource = $this->isClientDataSource();
$this->vars['clientDataSourceClass'] = $isClientDataSource ? 'client' : 'server';
$this->vars['data'] = json_encode(
$isClientDataSource ? $this->dataSource->getAllRecords() : []
);
}
//
// Internals
//
/**
* @inheritDoc
*/
protected function loadAssets()
{
$this->addCss('css/table.css', 'core');
if (Config::get('develop.decompileBackendAssets', false)) {
$scripts = Backend::decompileAsset($this->getAssetPath('js/build.js'));
foreach ($scripts as $script) {
$this->addJs($script, 'core');
}
} else {
$this->addJs('js/build-min.js', 'core');
}
}
/**
* Converts the columns associative array to a regular array and translates column headers and drop-down options.
* Working with regular arrays is much faster in JavaScript.
* References:
* - https://www.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
* - https://jsperf.com/performance-of-array-vs-object/3
*/
protected function prepareColumnsArray()
{
$result = [];
foreach ($this->columns as $key => $data) {
$data['key'] = $key;
if (isset($data['title'])) {
$data['title'] = trans($data['title']);
}
if (isset($data['options'])) {
foreach ($data['options'] as &$option) {
$option = trans($option);
}
}
if (isset($data['validation'])) {
foreach ($data['validation'] as &$validation) {
if (isset($validation['message'])) {
$validation['message'] = trans($validation['message']);
}
}
}
$result[] = $data;
}
return $result;
}
protected function isClientDataSource()
{
return $this->dataSource instanceof \Backend\Widgets\Table\ClientMemoryDataSource;
}
//
// Event handlers
//
public function onServerGetRecords()
{
// Disable asset broadcasting
$this->controller->flushAssets();
if ($this->isClientDataSource()) {
throw new SystemException('The Table widget is not configured to use the server data source.');
}
$count = post('count');
// Oddly, JS may pass false as a string (@todo)
if ($count === 'false') {
$count = false;
}
return [
'records' => $this->dataSource->getRecords(post('offset'), $count),
'count' => $this->dataSource->getCount()
];
}
public function onServerSearchRecords()
{
// Disable asset broadcasting
$this->controller->flushAssets();
if ($this->isClientDataSource()) {
throw new SystemException('The Table widget is not configured to use the server data source.');
}
$count = post('count');
// Oddly, JS may pass false as a string (@todo)
if ($count === 'false') {
$count = false;
}
return [
'records' => $this->dataSource->searchRecords(post('query'), post('offset'), $count),
'count' => $this->dataSource->getCount()
];
}
public function onServerCreateRecord()
{
if ($this->isClientDataSource()) {
throw new SystemException('The Table widget is not configured to use the server data source.');
}
$this->dataSource->createRecord(
post('recordData'),
post('placement'),
post('relativeToKey')
);
return $this->onServerGetRecords();
}
public function onServerUpdateRecord()
{
if ($this->isClientDataSource()) {
throw new SystemException('The Table widget is not configured to use the server data source.');
}
$this->dataSource->updateRecord(post('key'), post('recordData'));
}
public function onServerDeleteRecord()
{
if ($this->isClientDataSource()) {
throw new SystemException('The Table widget is not configured to use the server data source.');
}
$this->dataSource->deleteRecord(post('key'));
return $this->onServerGetRecords();
}
public function onGetDropdownOptions()
{
$columnName = Input::get('column');
$rowData = Input::get('rowData');
$eventResults = $this->fireEvent('table.getDropdownOptions', [$columnName, $rowData]);
$options = [];
if (count($eventResults)) {
$options = $eventResults[0];
}
return [
'options' => $options
];
}
public function onGetAutocompleteOptions()
{
$columnName = Input::get('column');
$rowData = Input::get('rowData');
$eventResults = $this->fireEvent('table.getAutocompleteOptions', [$columnName, $rowData]);
$options = [];
if (count($eventResults)) {
$options = $eventResults[0];
}
return [
'options' => $options
];
}
}

View File

@@ -0,0 +1,106 @@
<?php namespace Backend\Widgets;
use Backend\Classes\WidgetBase;
/**
* Toolbar Widget
* Used for building a toolbar, renders a toolbar.
*
* @package winter\wn-backend-module
* @author Alexey Bobkov, Samuel Georges
*/
class Toolbar extends WidgetBase
{
//
// Configurable properties
//
/**
* @var string Partial name containing the toolbar buttons
*/
public $buttons;
/**
* @var array|string Search widget configuration or partial name, optional.
*/
public $search;
//
// Object properties
//
/**
* @inheritDoc
*/
protected $defaultAlias = 'toolbar';
/**
* @var WidgetBase Reference to the search widget object.
*/
protected $searchWidget;
/**
* @var array List of CSS classes to apply to the toolbar container element
*/
public $cssClasses = [];
/**
* Initialize the widget, called by the constructor and free from its parameters.
*/
public function init()
{
$this->fillFromConfig([
'buttons',
'search',
]);
/*
* Prepare the search widget (optional)
*/
if (isset($this->search)) {
if (is_string($this->search)) {
$searchConfig = $this->makeConfig(['partial' => $this->search]);
}
else {
$searchConfig = $this->makeConfig($this->search);
}
$searchConfig->alias = $this->alias . 'Search';
$this->searchWidget = $this->makeWidget('Backend\Widgets\Search', $searchConfig);
$this->searchWidget->bindToController();
}
}
/**
* Renders the widget.
*/
public function render()
{
$this->prepareVars();
return $this->makePartial('toolbar');
}
/**
* Prepares the view data
*/
public function prepareVars()
{
$this->vars['search'] = $this->searchWidget ? $this->searchWidget->render() : '';
$this->vars['cssClasses'] = implode(' ', $this->cssClasses);
$this->vars['controlPanel'] = $this->makeControlPanel();
}
public function getSearchWidget()
{
return $this->searchWidget;
}
public function makeControlPanel()
{
if (!isset($this->buttons)) {
return false;
}
return $this->controller->makePartial($this->buttons, $this->vars);
}
}

View File

@@ -0,0 +1,10 @@
<div
id="<?= $this->getId(); ?>"
class="control-filter <?= $cssClasses ?>"
data-control="filterwidget"
data-options-handler="<?= $this->getEventHandler('onFilterGetOptions') ?>"
data-update-handler="<?= $this->getEventHandler('onFilterUpdate') ?>">
<?= $this->makePartial('filter_scopes') ?>
</div>

View File

@@ -0,0 +1,3 @@
<?php foreach ($scopes as $scope): ?>
<?= $this->renderScopeElement($scope) ?>
<?php endforeach ?>

View File

@@ -0,0 +1,14 @@
<div
class="filter-scope button-group"
data-scope-name="<?= e($scope->scopeName) ?>"
<?= ($scope->config['required'] ?? false) ? 'data-scope-required="true"' : '' ?>
>
<?php foreach ($scope->options as $key => $label): ?>
<button
class="btn <?= $scope->value === $key ? 'btn-primary' : 'btn-default' ?>"
data-scope-name="<?= e($scope->scopeName) ?>"
data-scope-value="<?= e($key) ?>">
<?= e($label) ?>
</button>
<?php endforeach ?>
</div>

View File

@@ -0,0 +1,7 @@
<!-- Checkbox scope -->
<div
class="filter-scope checkbox custom-checkbox"
data-scope-name="<?= $scope->scopeName ?>">
<input type="checkbox" id="<?= $scope->getId() ?>" <?= $scope->value ? 'checked' : '' ?> />
<label for="<?= $scope->getId() ?>"><?= e(trans($scope->label)) ?></label>
</div>

View File

@@ -0,0 +1,17 @@
<!-- Date scope -->
<a
class="filter-scope-date filter-has-popover <?= isset($date) ? 'active' : '' ?>"
href="javascript:;"
data-scope-name="<?= $scope->scopeName ?>"
data-scope-data="<?= e(json_encode([
'date' => isset($date) ? $date : null,
'minDate' => $scope->minDate,
'maxDate' => $scope->maxDate,
'firstDay' => $scope->firstDay,
'yearRange' => $scope->yearRange,
])) ?>"
<?= $scope->ignoreTimezone ? 'data-ignore-timezone' : ''; ?>
>
<span class="filter-label"><?= e(trans($scope->label)) ?>:</span>
<span class="filter-setting"><?= isset($dateStr) ? $dateStr : e(trans('backend::lang.filter.date_all')) ?></span>
</a>

View File

@@ -0,0 +1,17 @@
<!-- Date Range scope -->
<a
class="filter-scope-date filter-has-popover range <?= isset($after) || isset($before) ? 'active' : '' ?>"
href="javascript:;"
data-scope-name="<?= $scope->scopeName ?>"
data-scope-data="<?= e(json_encode([
'dates' => [isset($after) ? $after : null, isset($before) ? $before : null],
'minDate' => $scope->minDate,
'maxDate' => $scope->maxDate,
'firstDay' => $scope->firstDay,
'yearRange' => $scope->yearRange,
])) ?>"
<?= $scope->ignoreTimezone ? 'data-ignore-timezone' : ''; ?>
>
<span class="filter-label"><?= e(trans($scope->label)) ?>:</span>
<span class="filter-setting"><?= isset($afterStr) && isset($beforeStr) ? ($afterStr . ' → ' . $beforeStr) : e(trans('backend::lang.filter.date_all')) ?></span>
</a>

View File

@@ -0,0 +1,31 @@
<?php
$required = $scope->config['required'] ?? false;
$emptyOption = $scope->config['emptyOption'] ?? $scope->label ?? Lang::get('backend::lang.form.select_placeholder');
$hasEmpty = !$required && $emptyOption;
$selectedValue = $scope->value ?? null;
// If required and no default, preselect first option
if ($required && $selectedValue === null && !empty($scope->options)) {
reset($scope->options);
$selectedValue = key($scope->options);
}
?>
<div class="filter-scope dropdown" data-scope-name="<?= e($scope->scopeName) ?>">
<select
class="form-control custom-select select-no-search"
data-placeholder="<?= e($emptyOption); ?>"
data-dropdown-auto-width="true"
data-width="resolve"
<?= $required ? 'data-allow-clear="false"' : ''; ?>
name="<?= e($scope->scopeName) ?>"
>
<?php if ($hasEmpty): ?>
<option value="" <?= $selectedValue === null || $selectedValue === '' ? 'selected' : '' ?>></option>
<?php endif; ?>
<?php foreach ($scope->options as $key => $label): ?>
<option value="<?= e($key) ?>" <?= $selectedValue == $key ? 'selected' : '' ?>>
<?= e($label) ?>
</option>
<?php endforeach ?>
</select>
</div>

View File

@@ -0,0 +1,12 @@
<!-- Group scope -->
<a
class="filter-scope <?= $scope->value ? 'active' : '' ?>"
href="javascript:;"
data-scope-name="<?= $scope->scopeName ?>"
<?php if ($depends = $this->getScopeDepends($scope)): ?>
data-scope-depends="<?= $depends ?>"
<?php endif ?>
>
<span class="filter-label"><?= e(trans($scope->label)) ?>:</span>
<span class="filter-setting"><?= $scope->value ? count($scope->value) : e(trans('backend::lang.filter.all')) ?></span>
</a>

View File

@@ -0,0 +1,15 @@
<!-- Number scope -->
<a
class="filter-scope-number filter-has-popover <?= isset($number) ? 'active' : '' ?>"
href="javascript:;"
data-scope-name="<?= $scope->scopeName ?>"
data-scope-data="<?= e(json_encode([
'number' => isset($number) ? $number : null,
'step' => isset($step) ? $step : null,
'minValue' => isset($minValue) ? $minValue : null,
'maxValue' => isset($maxValue) ? $maxValue : null,
]))
?>">
<span class="filter-label"><?= e(trans($scope->label)) ?>:</span>
<span class="filter-setting"><?= isset($number) ? $number : e(trans('backend::lang.filter.number_all')) ?></span>
</a>

View File

@@ -0,0 +1,15 @@
<!-- Number Range scope -->
<a
class="filter-scope-number filter-has-popover range <?= isset($min) || isset($max) ? 'active' : '' ?>"
href="javascript:;"
data-scope-name="<?= $scope->scopeName ?>"
data-scope-data="<?= e(json_encode([
'numbers' => [isset($min) ? $min : null, isset($max) ? $max : null],
'step' => isset($step) ? $step : null,
'minValue' => isset($minValue) ? $minValue : null,
'maxValue' => isset($maxValue) ? $maxValue : null,
]))
?>">
<span class="filter-label"><?= e(trans($scope->label)) ?>:</span>
<span class="filter-setting"><?= isset($minStr) && isset($maxStr) ? ($minStr . ' → ' . $maxStr) : e(trans('backend::lang.filter.number_all')) ?></span>
</a>

View File

@@ -0,0 +1,7 @@
<!-- Switch scope -->
<div
class="filter-scope checkbox custom-checkbox is-indeterminate"
data-scope-name="<?= $scope->scopeName ?>">
<input type="checkbox" id="<?= $scope->getId() ?>" data-checked="<?= $scope->value ?: '0' ?>" />
<label for="<?= $scope->getId() ?>"><?= e(trans($scope->label)) ?></label>
</div>

View File

@@ -0,0 +1,15 @@
<div class="filter-scope text loading-indicator-container size-input-text">
<label class="filter-label">
<?= e(trans($scope->label)) ?>:
<input type="text"
name="options[value][<?= $scope->scopeName ?>]"
data-request="<?= $this->getEventHandler('onFilterUpdate') ?>"
data-request-data="'scopeName':'<?= $scope->scopeName ?>'"
data-track-input
data-load-indicator
data-load-indicator-opaque
size="<?= $size ?>"
value="<?= isset($value) ? e($value):''; ?>"
class="form-control"/>
</label>
</div>

View File

@@ -0,0 +1,391 @@
/*
* Form Widget
*
* Dependences:
* - Nil
*/
+function ($) { "use strict";
var Base = $.wn.foundation.base,
BaseProto = Base.prototype
var FormWidget = function (element, options) {
this.$el = $(element)
this.options = options || {}
this.fieldElementCache = null
/*
* Throttle dependency updating
*/
this.dependantUpdateInterval = 300
this.dependantUpdateTimers = {}
$.wn.foundation.controlUtils.markDisposable(element)
Base.call(this)
this.init()
}
FormWidget.prototype = Object.create(BaseProto)
FormWidget.prototype.constructor = FormWidget
FormWidget.prototype.init = function() {
this.$form = this.$el.closest('form')
this.bindDependants()
this.bindCheckboxlist()
this.toggleEmptyTabs()
this.bindLazyTabs()
this.bindCollapsibleSections()
this.$el.on('oc.triggerOn.afterUpdate', this.proxy(this.toggleEmptyTabs))
this.$el.one('dispose-control', this.proxy(this.dispose))
}
FormWidget.prototype.dispose = function() {
this.unbindDependants()
this.unbindCheckboxList()
this.unbindLazyTabs()
this.unbindCollapsibleSections()
this.$el.off('dispose-control', this.proxy(this.dispose))
this.$el.removeData('oc.formwidget')
this.$el = null
this.$form = null
this.options = null
this.fieldElementCache = null
BaseProto.dispose.call(this)
}
/*
* Logic for checkboxlist
*/
FormWidget.prototype.bindCheckboxlist = function() {
var checkAllBoxes = function($field, flag) {
$('input[type=checkbox]', $field)
.prop('checked', flag)
.first()
.trigger('change')
}
this.$el.on('click', '[data-field-checkboxlist-all]', function() {
checkAllBoxes($(this).closest('.field-checkboxlist'), true)
})
this.$el.on('click', '[data-field-checkboxlist-none]', function() {
checkAllBoxes($(this).closest('.field-checkboxlist'), false)
})
}
/*
* Unbind checkboxlist handlers
*/
FormWidget.prototype.unbindCheckboxList = function() {
this.$el.off('click', '[data-field-checkboxlist-all]')
this.$el.off('click', '[data-field-checkboxlist-none]')
}
/*
* Get all fields elements that belong to this form, nested form
* fields are removed from this collection.
*/
FormWidget.prototype.getFieldElements = function() {
if (this.fieldElementCache !== null) {
return this.fieldElementCache
}
var form = this.$el,
nestedFields = form.find('[data-control="formwidget"] [data-field-name]')
return this.fieldElementCache = form.find('[data-field-name]').not(nestedFields)
}
/*
* Bind dependant fields
*/
FormWidget.prototype.bindDependants = function() {
var self = this,
fieldMap = this._getDependants()
/*
* When a field is updated, refresh its dependents
*/
$.each(fieldMap, function(fieldName, toRefresh) {
$(document).on('change.oc.formwidget',
'[data-field-name="' + fieldName + '"]',
$.proxy(self.onRefreshDependants, self, fieldName, toRefresh)
)
})
}
/*
* Dispose of the dependant field handlers
*/
FormWidget.prototype.unbindDependants = function() {
var fieldMap = this._getDependants()
$.each(fieldMap, function(fieldName, toRefresh) {
$(document).off('change.oc.formwidget', '[data-field-name="' + fieldName + '"]')
})
}
/*
* Retrieve the dependant fields
*/
FormWidget.prototype._getDependants = function() {
if (!$('[data-field-depends]', this.$el).length) {
return;
}
var fieldMap = {},
fieldElements = this.getFieldElements()
/*
* Map master and slave fields
*/
fieldElements.filter('[data-field-depends]').each(function() {
var name = $(this).data('field-name'),
depends = $(this).data('field-depends')
$.each(depends, function(index, depend){
if (!fieldMap[depend]) {
fieldMap[depend] = { fields: [] }
}
fieldMap[depend].fields.push(name)
})
})
return fieldMap
}
/*
* Refresh a dependancy field
* Uses a throttle to prevent duplicate calls and click spamming.
*
* The event parameter is passed automatically by jQuery as the third
* argument (after the two preset arguments from $.proxy). It carries
* a cascadeChain array that tracks which fields have already been
* refreshed in the current cascade, preventing infinite loops when
* fields have circular dependsOn declarations.
*/
FormWidget.prototype.onRefreshDependants = function(fieldName, toRefresh, event) {
var self = this,
form = this.$el,
formEl = this.$form,
fieldElements = this.getFieldElements(),
cascadeChain = (event && event.cascadeChain) || []
/*
* If this field already appears in the cascade chain, we have
* a circular dependency. Stop the cascade to prevent an
* infinite loop. See: https://github.com/wintercms/winter/issues/421
*/
if (cascadeChain.indexOf(fieldName) !== -1) {
return
}
if (this.dependantUpdateTimers[fieldName] !== undefined) {
window.clearTimeout(this.dependantUpdateTimers[fieldName])
}
this.dependantUpdateTimers[fieldName] = window.setTimeout(function() {
var refreshData = $.extend({},
toRefresh,
paramToObj('data-refresh-data', self.options.refreshData)
)
formEl.request(self.options.refreshHandler, {
data: refreshData
}).success(function() {
self.toggleEmptyTabs()
var newChain = cascadeChain.concat([fieldName])
$.each(toRefresh.fields, function(key, field) {
var cascadeEvent = $.Event('change')
cascadeEvent.cascadeChain = newChain
$('[data-field-name="' + field + '"]').trigger(cascadeEvent)
})
})
}, this.dependantUpdateInterval)
$.each(toRefresh.fields, function(index, field) {
fieldElements.filter('[data-field-name="'+field+'"]:visible')
.addClass('loading-indicator-container size-form-field')
.loadIndicator()
})
}
/*
* Render tab form fields once a lazy tab is selected.
*/
FormWidget.prototype.bindLazyTabs = function() {
var tabControl = $('[data-control=tab]', this.$el),
tabContainer = $('.nav-tabs', tabControl)
tabContainer.on('click', '.tab-lazy [data-toggle="tab"]', function() {
var $el = $(this),
handlerName = $el.data('tab-lazy-handler')
$.request(handlerName, {
data: {
target: $el.data('target'),
name: $el.data('tab-name'),
section: $el.data('tab-section'),
},
success: function(data) {
this.success(data)
$el.parent().removeClass('tab-lazy')
// Trigger all input presets to populate new fields.
setTimeout(function() {
$('[data-input-preset]').each(function() {
var preset = $(this).data('oc.inputPreset')
if (preset && preset.$src) {
preset.$src.trigger('input')
}
})
}, 0)
}
})
})
// If initial active tab is lazy loaded, load it immediately
if ($('> li.active.tab-lazy', tabContainer).length) {
$('> li.active.tab-lazy > [data-toggle="tab"]', tabContainer).trigger('click')
}
}
/*
* Unbind the lazy tab handlers
*/
FormWidget.prototype.unbindLazyTabs = function() {
var tabControl = $('[data-control=tab]', this.$el)
$('.nav-tabs', tabControl).off('click', '.tab-lazy [data-toggle="tab"]')
}
/*
* Hides tabs that have no content, it is possible this can be
* called multiple times in a single cycle due to input.trigger.
*/
FormWidget.prototype.toggleEmptyTabs = function() {
var self = this,
form = this.$el
if (this.toggleEmptyTabsTimer !== undefined) {
window.clearTimeout(this.toggleEmptyTabsTimer)
}
this.toggleEmptyTabsTimer = window.setTimeout(function() {
var tabControl = $('[data-control=tab]', self.$el),
tabContainer = $('.nav-tabs', tabControl)
if (!tabControl.length || !form || !form.length || !$.contains(form.get(0), tabControl.get(0)))
return
/*
* Check each tab pane for form field groups
*/
$('.tab-pane:not(.lazy)', tabControl).each(function() {
$('[data-target="#' + $(this).attr('id') + '"]', tabControl)
.closest('li')
.toggle(!!$('> .form-group:not(:empty):not(.hide)', $(this)).length)
})
/*
* If a hidden tab was selected, select the first visible tab
*/
if (!$('> li.active:visible', tabContainer).length) {
$('> li:visible:first', tabContainer)
.find('> a:first')
.tab('show')
}
}, 1)
}
/*
* Makes sections collapsible by targeting every field after
* up until the next section
*/
FormWidget.prototype.bindCollapsibleSections = function() {
$('.section-field[data-field-collapsible]', this.$form)
.addClass('collapsed')
.find('.field-section:first')
.addClass('is-collapsible')
.end()
.on('click', function() {
$(this)
.toggleClass('collapsed')
.nextUntil('.section-field').toggle()
})
.nextUntil('.section-field').hide()
}
/*
* Unbinds collapsible section handlers
*/
FormWidget.prototype.unbindCollapsibleSections = function() {
$('.section-field[data-field-collapsible]', this.$form).off('click')
}
FormWidget.DEFAULTS = {
refreshHandler: null,
refreshData: {}
}
// FORM WIDGET PLUGIN DEFINITION
// ============================
var old = $.fn.formWidget
$.fn.formWidget = function (option) {
var args = arguments,
result
this.each(function () {
var $this = $(this)
var data = $this.data('oc.formwidget')
var options = $.extend({}, FormWidget.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.formwidget', (data = new FormWidget(this, options)))
if (typeof option == 'string') result = data[option].call($this)
if (typeof result != 'undefined') return false
})
return result ? result : this
}
$.fn.formWidget.Constructor = FormWidget
// FORM WIDGET NO CONFLICT
// =================
$.fn.formWidget.noConflict = function () {
$.fn.formWidget = old
return this
}
// FORM WIDGET DATA-API
// ==============
function paramToObj(name, value) {
if (value === undefined) value = ''
if (typeof value == 'object') return value
try {
return ocJSON("{" + value + "}")
}
catch (e) {
throw new Error('Error parsing the '+name+' attribute value. '+e)
}
}
$(document).render(function() {
$('[data-control="formwidget"]').formWidget();
})
}(window.jQuery);

View File

@@ -0,0 +1,11 @@
<div
class="form-group <?= $this->previewMode ? 'form-group-preview' : '' ?> <?= $field->type ?>-field span-<?= $field->span ?> <?= $field->required?'is-required':'' ?> <?= $field->stretch?'layout-relative':'' ?> <?= $field->cssClass ?>"
<?php if ($depends = $this->getFieldDepends($field)): ?>
data-field-depends="<?= $depends ?>"
<?php endif ?>
data-field-name="<?= $field->fieldName ?>"
<?= $field->getAttributes('container') ?>
id="<?= $field->getId('group') ?>"><?=
/* Must be on the same line for :empty selector */
trim($this->makePartial('field', ['field' => $field]))
?></div>

View File

@@ -0,0 +1,30 @@
<?php if (!$field->hidden): ?>
<?php if (!$this->showFieldLabels($field)): ?>
<?= $this->renderFieldElement($field) ?>
<?php else: ?>
<?php
$fieldComment = $field->commentHtml ? trans($field->comment) : e(trans($field->comment));
?>
<?php if ($field->label): ?>
<label for="<?= $field->getId() ?>">
<?= e(trans($field->label)) ?>
</label>
<?php endif ?>
<?php if ($field->comment && $field->commentPosition == 'above'): ?>
<p class="help-block before-field"><?= $fieldComment ?></p>
<?php endif ?>
<?= $this->renderFieldElement($field) ?>
<?php if ($field->comment && $field->commentPosition == 'below'): ?>
<p class="help-block"><?= $fieldComment ?></p>
<?php endif ?>
<?php endif ?>
<?php endif ?>

View File

@@ -0,0 +1,22 @@
<?php
$fieldOptions = $field->options();
?>
<!-- Balloon selector -->
<div
data-control="balloon-selector"
id="<?= $field->getId() ?>"
class="control-balloon-selector <?= $this->previewMode || $field->disabled ? 'control-disabled' : '' ?>"
<?= $field->getAttributes() ?>>
<ul>
<?php foreach ($fieldOptions as $value => $text): ?>
<li data-value="<?= e($value) ?>" class="<?= $field->isSelected($value) ? 'active' : '' ?>"><?= e(trans($text)) ?></li>
<?php endforeach ?>
</ul>
<input
type="hidden"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($field->value) ?>"
/>
</div>

View File

@@ -0,0 +1,74 @@
<?php
/**
* @var \Winter\Storm\Database\Model $formModel
* @var \Backend\Classes\FormField $field
*/
$action = 'button';
$handler = null;
$href = null;
$target = null;
if (!empty($field->config['href']) || filter_var($field->value, FILTER_VALIDATE_URL)) {
$action = 'link';
$href = $field->config['href'] ?? '';
$target = $field->config['target'] ?? null;
if ($formModel->hasAttribute($href)) {
$href = $formModel->getAttribute($href);
}
if (filter_var($field->value, FILTER_VALIDATE_URL)) {
$href = $field->value;
}
} elseif (!empty($field->config['handler'])) {
$action = 'popup';
$handler = $field->config['handler'];
}
$element = $action === 'link' ? 'a' : 'button';
$label = $field->config['buttonLabel'] ?? '';
$buttonType = $field->config['buttonType'] ?? 'default';
$classes = implode(' ', array_filter([
"btn btn-$buttonType",
$field->config['buttonCssClass'] ?? ''
]));
$request = $field->config['request'] ?? '';
$loadingText = $field->config['loading'] ?? '';
$icon = $field->config['icon'] ?? '';
?>
<div class="loading-indicator-container">
<?php if ($field->path): ?>
<?= $this->controller->makePartial($field->path, [
'formWidget' => $this,
'formModel' => $formModel,
'formField' => $field,
'formValue' => $field->value,
'model' => $formModel,
'field' => $field,
'value' => $field->value,
'action' => $action,
'element' => $element,
'label' => $label,
'buttonType' => $buttonType,
'classes' => $classes,
'handler' => $handler,
'request' => $request,
'href' => $href,
'target' => $target,
'loading' => $loadingText,
'icon' => $icon,
]) ?>
<?php else: ?>
<<?= e($element); ?>
class="<?= e($classes); ?>"
data-load-indicator<?= !empty($loadingText) ? '="' . e(trans($loadingText)) . '"' : ''; ?>
<?= $action === 'popup' ? 'data-control="popup"' : ''; ?>
<?= !empty($handler) ? 'data-handler="' . e($handler) . '"' : ''; ?>
<?= !empty($request) ? 'data-request="' . e($request) . '"' : ''; ?>
<?= !empty($href) ? 'href="' . e($href) . '"' : ''; ?>
<?= !empty($target) ? 'target="' . e($target) . '"' : ''; ?>
>
<?= !empty($icon) ? '<i class="' . e($icon) . '"></i>' : '' ?>
<?= e(trans($label)); ?>
</<?= e($element); ?>>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,23 @@
<!-- Checkbox -->
<div class="checkbox custom-checkbox" tabindex="0">
<input
type="hidden"
name="<?= $field->getName() ?>"
value="0"
<?= $this->previewMode ? 'disabled="disabled"' : '' ?>>
<input
type="checkbox"
id="<?= $field->getId() ?>"
name="<?= $field->getName() ?>"
value="1"
<?= $this->previewMode ? 'disabled="disabled"' : '' ?>
<?= $field->isSelected() ? 'checked="checked"' : '' ?>
<?= $field->getAttributes() ?>>
<label for="<?= $field->getId() ?>">
<?= e(trans($field->label)) ?>
</label>
<?php if ($field->comment): ?>
<p class="help-block"><?= $field->commentHtml ? trans($field->comment) : e(trans($field->comment)) ?></p>
<?php endif ?>
</div>

View File

@@ -0,0 +1,120 @@
<?php
$fieldOptions = $field->options();
$checkedValues = (array) $field->value;
$isScrollable = count($fieldOptions) > 10;
$readOnly = $this->previewMode || $field->readOnly || $field->disabled;
$quickselectEnabled = $field->getConfig('quickselect', $isScrollable);
?>
<!-- Checkbox List -->
<?php if ($readOnly && $field->value): ?>
<div class="field-checkboxlist">
<?php
$index = 0;
foreach ($fieldOptions as $value => $option):
$index++;
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
if (!in_array($value, $checkedValues)) {
continue;
}
if (!is_array($option)) {
$option = [$option];
}
?>
<div class="checkbox custom-checkbox">
<input
type="checkbox"
id="<?= $checkboxId ?>"
name="<?= $field->getName() ?>[]"
value="<?= e($value) ?>"
disabled="disabled"
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 ?>
</div>
<?php elseif (count($fieldOptions)): ?>
<div class="field-checkboxlist <?= $isScrollable ? 'is-scrollable' : '' ?>">
<?php if ($quickselectEnabled): ?>
<!-- Quick selection -->
<div class="checkboxlist-controls">
<div>
<a href="javascript:;" data-field-checkboxlist-all>
<i class="icon-check-square"></i> <?= e(trans('backend::lang.form.select_all')) ?>
</a>
</div>
<div>
<a href="javascript:;" data-field-checkboxlist-none>
<i class="icon-eraser"></i> <?= e(trans('backend::lang.form.select_none')) ?>
</a>
</div>
</div>
<?php endif ?>
<div class="field-checkboxlist-inner">
<?php if ($isScrollable): ?>
<!-- Scrollable Checkbox list -->
<div class="field-checkboxlist-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):
$index++;
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
if (!is_array($option)) {
$option = [$option];
}
?>
<div class="checkbox custom-checkbox">
<input
type="checkbox"
id="<?= $checkboxId ?>"
name="<?= $field->getName() ?>[]"
value="<?= e($value) ?>"
<?= $readOnly ? 'disabled="disabled"' : '' ?>
<?= 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 ($isScrollable): ?>
</div>
</div>
<?php endif ?>
</div>
</div>
<?php else: ?>
<!-- No options specified -->
<?php if ($field->placeholder): ?>
<p><?= e(trans($field->placeholder)) ?></p>
<?php endif ?>
<?php endif ?>

View File

@@ -0,0 +1,43 @@
<?php
$fieldOptions = $field->options();
if ($fieldOptions instanceof Illuminate\Support\Collection) {
$fieldOptions = $fieldOptions->all();
}
?>
<!-- Dropdown -->
<?php if ($this->previewMode || $field->readOnly): ?>
<div class="form-control" <?= $field->readOnly ? 'disabled="disabled"' : ''; ?>>
<?= (isset($fieldOptions[$field->value])) ? e(trans($fieldOptions[$field->value])) : '' ?>
</div>
<input type="hidden" name="<?= $field->getName() ?>" value="<?= $field->value ?>">
<?php else:
$emptyOption = $field->getConfig('emptyOption', $field->placeholder);
$options = $field->getAttributes(htmlBuild:false);
$options['id'] = $field->getId();
$options['class'] = 'form-control custom-select';
if ($field->getConfig('showSearch', true) === false) {
$options['class'] .= ' select-no-search';
}
if ($field->getConfig('allowCustom', false)) {
$options['class'] .= ' select-modifiable';
}
if ($emptyOption) {
$options['emptyOption'] = e(trans($emptyOption));
}
if ($field->placeholder) {
$options['data-placeholder'] = e(trans($field->placeholder));
}
foreach ($fieldOptions as $key => &$value) {
if (is_string($value) && str_contains($value, '::')) {
$value = e(trans($value));
}
}
?>
<?= Form::select(
name: $field->getName(),
list: $fieldOptions,
selected: $field->value,
options: $options
) ?>
<?php endif ?>

View File

@@ -0,0 +1,30 @@
<!-- Email -->
<div class="input-group static">
<span class="input-group-addon">
<i class="empty wn-icon-envelope"></i>
</span>
<?php if ($this->previewMode): ?>
<?php if ($field->value): ?>
<a
href="mailto:<?= e($field->value) ?>"
target="_blank"
rel="noopener noreferrer"
class="form-control"
>
<?= e($field->value) ?>
</a>
<?php else: ?>
<span class="form-control">&nbsp;</span>
<?php endif ?>
<?php else: ?>
<input
type="email"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($field->value) ?>"
placeholder="<?= e(trans($field->placeholder)) ?>"
class="form-control"
<?= $field->getAttributes() ?>
/>
<?php endif ?>
</div>

View File

@@ -0,0 +1,8 @@
<?= $this->controller->makeHintPartial($field->getId(), $field->path ?: $field->fieldName, [
'formModel' => $formModel,
'formField' => $field,
'formValue' => $field->value,
'model' => $formModel,
'field' => $field,
'value' => $field->value
]) ?>

View File

@@ -0,0 +1,26 @@
<!-- Number -->
<?php if ($this->previewMode): ?>
<span class="form-control"><?= isset($field->value) ? e($field->value) : '&nbsp;' ?></span>
<?php else: ?>
<?php
$min = isset($field->config['min']) ? $field->config['min'] : false;
$max = isset($field->config['max']) ? $field->config['max'] : false;
$step = isset($field->config['step']) ? $field->config['step'] : 'any';
?>
<input
type="number"
step="<?= $step ?>"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($field->value) ?>"
placeholder="<?= e(trans($field->placeholder)) ?>"
class="form-control"
autocomplete="off"
<?= $min !== false ? 'min="' . $min . '"' : ''; ?>
<?= $max !== false ? 'max="' . $max . '"' : ''; ?>
<?= $field->hasAttribute('pattern') ? '' : 'pattern="-?\d+(\.\d+)?"' ?>
<?= $field->hasAttribute('maxlength') ? '' : 'maxlength="255"' ?>
<?= $field->getAttributes() ?>
/>
<?php endif ?>

View File

@@ -0,0 +1,9 @@
<?= $this->controller->makePartial($field->path ?: $field->fieldName, [
'formWidget' => $this,
'formModel' => $formModel,
'formField' => $field,
'formValue' => $field->value,
'model' => $formModel,
'field' => $field,
'value' => $field->value
]) ?>

View File

@@ -0,0 +1,16 @@
<!-- Password -->
<?php if ($this->previewMode): ?>
<div class="form-control">********</div>
<?php else: ?>
<input
type="password"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value=""
placeholder="<?= e(trans($field->placeholder)) ?>"
class="form-control"
<?= $field->hasAttribute('autocomplete') ? '' : 'autocomplete="new-password"' ?>
<?= $field->hasAttribute('maxlength') ? '' : 'maxlength="255"' ?>
<?= $field->getAttributes() ?>
/>
<?php endif ?>

View File

@@ -0,0 +1,43 @@
<?php
$fieldOptions = $field->options();
?>
<!-- Radio List -->
<?php if (count($fieldOptions)): ?>
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
<?php
$index++;
if (is_string($option)) {
$option = array($option);
}
$fieldId = md5(uniqid($field->getId($index), true));
?>
<div class="radio custom-radio">
<input
id="<?= $fieldId ?>"
name="<?= $field->getName() ?>"
value="<?= e($value) ?>"
type="radio"
<?= $field->isSelected($value) ? 'checked="checked"' : '' ?>
<?= $this->previewMode ? 'disabled="disabled"' : '' ?>
<?= $field->getAttributes() ?>>
<label for="<?= $fieldId ?>">
<?= 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 else: ?>
<!-- No options specified -->
<?php if ($field->placeholder): ?>
<p><?= e(trans($field->placeholder)) ?></p>
<?php endif ?>
<?php endif ?>

View File

@@ -0,0 +1,40 @@
<!-- Range -->
<?php if ($this->previewMode): ?>
<span class="form-control"><?= isset($field->value) ? e($field->value) : '&nbsp;' ?></span>
<?php else: ?>
<?php
$min = $field->config['min'] ?? 0;
$max = $field->config['max'] ?? 100;
$step = $field->config['step'] ?? 1;
$value = $field->value;
if ($min > $max) {
$min = $max - $step;
}
if (is_null($value)) {
$value = ($min + $max) / 2;
}
?>
<input
type="range"
step="<?= $step ?>"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($value) ?>"
min="<?= $min ?>"
max="<?= $max ?>"
<?= $field->getAttributes() ?>
/>
<span style="position: absolute; transform: translateX(-50%)"></span>
<script>
(() => {
const input = document.getElementById("<?= $field->getId() ?>");
input.addEventListener("input", function () {
this.nextElementSibling.innerHTML = this.value;
var pos = ((this.value - <?= $min ?>) / (<?= $max ?> - <?= $min ?>) * 100);
this.nextElementSibling.style.left = `calc(${pos}% + ${8 - pos * 0.15}px)`;
});
input.dispatchEvent(new Event('input'));
})();
</script>
<?php endif; ?>

View File

@@ -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"><?= $field->commentHtml ? trans($field->comment) : e(trans($field->comment)) ?></p>
<?php endif ?>
</div>

View File

@@ -0,0 +1,38 @@
<?php
$previewMode = false;
if ($this->previewMode || $field->readOnly) {
$previewMode = true;
}
$on = isset($field->config['on']) ? $field->config['on'] : 'backend::lang.form.field_on';
$off = isset($field->config['off']) ? $field->config['off'] : 'backend::lang.form.field_off';
?>
<!-- Switch -->
<div class="<?= $previewMode ? 'disabled' : '' ?>">
<div class="field-switch">
<label for="<?= $field->getId() ?>"><?= e(trans($field->label)) ?></label>
<?php if ($field->comment): ?>
<p class="help-block"><?= $field->commentHtml ? trans($field->comment) : e(trans($field->comment)) ?></p>
<?php endif ?>
</div>
<input
type="hidden"
name="<?= $field->getName() ?>"
value="0"
<?= $previewMode ? 'disabled="disabled"' : '' ?>>
<label class="custom-switch" <?= $previewMode ? 'onclick="return false"' : '' ?>>
<input
type="checkbox"
id="<?= $field->getId() ?>"
name="<?= $field->getName() ?>"
value="1"
<?= $previewMode ? 'readonly="readonly"' : '' ?>
<?= $field->value == 1 ? 'checked="checked"' : '' ?>
<?= $field->getAttributes() ?>>
<span><span><?= e(trans($on)) ?></span><span><?= e(trans($off)) ?></span></span>
<a class="slide-button"></a>
</label>
</div>

View File

@@ -0,0 +1,49 @@
<!-- Tel (Phone) -->
<?php
$fieldOptions = $field->options();
$hasOptions = is_array($fieldOptions) && count($fieldOptions);
$listId = $hasOptions ? $field->getId() . '-list' : null;
?>
<div class="input-group static">
<span class="input-group-addon">
<i class="empty wn-icon-phone"></i>
</span>
<?php if ($this->previewMode): ?>
<?php if ($field->value): ?>
<a
href="tel:<?= e($field->value) ?>"
target="_blank"
rel="noopener noreferrer"
class="form-control"
>
<?= e($field->value) ?>
</a>
<?php else: ?>
<span class="form-control">&nbsp;</span>
<?php endif ?>
<?php else: ?>
<input
type="tel"
id="<?= $field->getId() ?>"
name="<?= $field->getName() ?>"
value="<?= e($field->value) ?>"
class="form-control"
<?= isset($field->autocomplete) && is_string($field->autocomplete) ? 'autocomplete="' . e($field->autocomplete) . '"' : '' ?>
<?= isset($field->maxlength) && is_numeric($field->maxlength) ? 'maxlength="' . e($field->maxlength) . '"' : '' ?>
<?= isset($field->minlength) && is_numeric($field->minlength) ? 'minlength="' . e($field->minlength) . '"' : '' ?>
<?= isset($field->pattern) && is_string($field->pattern) ? 'pattern="' . e($field->pattern) . '"' : '' ?>
<?= isset($field->placeholder) && is_string($field->placeholder) ? 'placeholder="' . e(trans($field->placeholder)) . '"' : '' ?>
<?= isset($field->size) && is_numeric($field->size) ? 'size="' . e($field->size) . '"' : '' ?>
<?= $field->getAttributes() ?>
<?= $listId ? 'list="' . e($listId) . '"' : '' ?>
/>
<?php if ($hasOptions): ?>
<datalist id="<?= e($listId) ?>">
<?php foreach ($fieldOptions as $value => $label): ?>
<?php $value = is_int($value) ? $label : $value ?>
<option value="<?= e($value) ?>"<?= $value !== $label ? ' label="' . e(trans($label)) . '"' : '' ?>></option>
<?php endforeach ?>
</datalist>
<?php endif ?>
<?php endif ?>
</div>

View File

@@ -0,0 +1,15 @@
<!-- Text -->
<?php if ($this->previewMode): ?>
<span class="form-control"><?= $field->value ? e($field->value) : '&nbsp;' ?></span>
<?php else: ?>
<input
type="text"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($field->value) ?>"
placeholder="<?= e(trans($field->placeholder)) ?>"
class="form-control"
autocomplete="off"
<?= $field->getAttributes() ?>
/>
<?php endif ?>

View File

@@ -0,0 +1,12 @@
<!-- Textarea -->
<?php if ($this->previewMode): ?>
<div class="form-control"><?= nl2br(e($field->value)) ?></div>
<?php else: ?>
<textarea
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
autocomplete="off"
class="form-control field-textarea size-<?= $field->size ?>"
placeholder="<?= e(trans($field->placeholder)) ?>"
<?= $field->getAttributes() ?>><?= e($field->value) ?></textarea>
<?php endif?>

View File

@@ -0,0 +1,49 @@
<!-- URL -->
<?php
$fieldOptions = $field->options();
$hasOptions = is_array($fieldOptions) && count($fieldOptions);
$listId = $hasOptions ? $field->getId() . '-list' : null;
?>
<div class="input-group static">
<span class="input-group-addon">
<i class="empty wn-icon-link"></i>
</span>
<?php if ($this->previewMode): ?>
<?php if ($field->value): ?>
<a
href="<?= e($field->value) ?>"
target="_blank"
rel="noopener noreferrer"
class="form-control"
>
<?= e($field->value) ?>
</a>
<?php else: ?>
<span class="form-control">&nbsp;</span>
<?php endif ?>
<?php else: ?>
<input
type="url"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($field->value) ?>"
class="form-control"
<?= isset($field->autocomplete) && is_string($field->autocomplete) ? 'autocomplete="' . e($field->autocomplete) . '"' : '' ?>
<?= isset($field->maxlength) && is_numeric($field->maxlength) ? 'maxlength="' . e($field->maxlength) . '"' : '' ?>
<?= isset($field->minlength) && is_numeric($field->minlength) ? 'minlength="' . e($field->minlength) . '"' : '' ?>
<?= isset($field->pattern) && is_string($field->pattern) ? 'pattern="' . e($field->pattern) . '"' : '' ?>
<?= isset($field->placeholder) && is_string($field->placeholder) ? 'placeholder="' . e(trans($field->placeholder)) . '"' : '' ?>
<?= isset($field->size) && is_numeric($field->size) ? 'size="' . e($field->size) . '"' : '' ?>
<?= $field->getAttributes() ?>
<?= $listId ? 'list="' . e($listId) . '"' : '' ?>
/>
<?php if ($hasOptions): ?>
<datalist id="<?= e($listId) ?>">
<?php foreach ($fieldOptions as $value => $label): ?>
<?php $value = is_int($value) ? $label : $value ?>
<option value="<?= e($value) ?>"<?= $value !== $label ? ' label="' . e(trans($label)) . '"' : '' ?>></option>
<?php endforeach ?>
</datalist>
<?php endif ?>
<?php endif ?>
</div>

View File

@@ -0,0 +1,5 @@
<!-- Widget -->
<?php
$widget = $this->makeFormFieldWidget($field);
?>
<?= $widget->render() ?>

View File

@@ -0,0 +1,10 @@
<div
data-control="formwidget"
data-refresh-handler="<?= $this->getEventHandler('onRefresh') ?>"
class="form-widget form-elements layout"
role="form"
id="<?= $this->getId() ?>">
<?= $this->makePartial('form') ?>
</div>

View File

@@ -0,0 +1,12 @@
<?php if ($outsideTabs->hasFields()): ?>
<?= $this->makePartial('section', ['tabs' => $outsideTabs]) ?>
<?php endif ?>
<?php if ($primaryTabs->hasFields()): ?>
<?= $this->makePartial('section', ['tabs' => $primaryTabs]) ?>
<?php endif ?>
<?php if ($secondaryTabs->hasFields()): ?>
<?= $this->makePartial('section', ['tabs' => $secondaryTabs]) ?>
<?php endif ?>

View File

@@ -0,0 +1,3 @@
<?php foreach ($fields as $field): ?>
<?= $this->makePartial('field-container', ['field' => $field]) ?>
<?php endforeach ?>

View File

@@ -0,0 +1,62 @@
<?php
$type = $tabs->section;
$navCss = '';
$contentCss = '';
$paneCss = '';
if ($tabs->stretch) {
$navCss = 'layout-row min-size';
$contentCss = 'layout-row';
$paneCss = 'layout-cell';
}
?>
<div class="<?= $navCss ?>">
<ul class="nav nav-tabs" <?= $tabs->linkable ? 'data-linkable' : '' ?>>
<?php
$index = 0;
foreach ($tabs as $name => $fields):
$lazy = in_array($name, $tabs->lazy);
?>
<li class="<?= ($index++ === 0) ? 'active' : '' ?> <?= $lazy ? 'tab-lazy' : '' ?>">
<a
href="#<?= $type . 'tab-' . ($tabs->linkable ? str_slug($name) : $index) ?>"
<?php if ($lazy): ?>
data-tab-name="<?= e($name) ?>"
data-tab-section="<?= $type ?>"
data-tab-lazy-handler="<?= $this->getEventHandler('onLazyLoadTab') ?>"
<?php endif ?>
>
<span class="title">
<span>
<?php if ($tabs->getIcon($name)): ?>
<span class="<?= $tabs->getIcon($name) ?>"></span>
<?php endif; ?>
<?= e(trans($name)) ?>
</span>
</span>
</a>
</li>
<?php endforeach ?>
</ul>
</div>
<div class="tab-content <?= $contentCss ?>">
<?php
$index = 0;
foreach ($tabs as $name => $fields):
$lazy = in_array($name, $tabs->lazy);
?>
<div
class="tab-pane <?= $lazy ? 'lazy' : '' ?> <?= e($tabs->getPaneCssClass($index, $name)) ?> <?= ($index++ === 0) ? 'active' : '' ?> <?= $paneCss ?>"
id="<?= $type . 'tab-' . $index ?>">
<?php if ($lazy): ?>
<?= $this->makePartial('form_tabs_lazy', ['fields' => $fields]) ?>
<?php else: ?>
<?= $this->makePartial('form_fields', ['fields' => $fields]) ?>
<?php endif ?>
</div>
<?php endforeach ?>
</div>

View File

@@ -0,0 +1,37 @@
<div class="loading-indicator-container m-t">
<div class="loading-indicator indicator-center">
<span></span>
</div>
</div>
<?php
// Do not create a hidden field for these field types since
// they don't contain any form data.
$ignoredTypes = ['section', 'partial'];
foreach ($fields as $field):
if (in_array($field->type, $ignoredTypes)) {
continue;
}
$isMultiValue = is_array($field->value);
foreach (array_wrap($field->value) as $index => $value):
// Use array field names if the field has multiple values (repeater, checkboxlist, etc.).
$fieldName = $isMultiValue ? sprintf('%s[%s]', $field->getName(), $index) : $field->getName();
$valueIsArray = is_array($value);
foreach (array_wrap($value) as $index => $value):
// Set the correct array keys if the value is an array (repeater form fields).
$currentFieldName = $valueIsArray ? sprintf('%s[%s]', $fieldName, $index) : $fieldName;
?>
<input
type="hidden"
name="<?= $currentFieldName ?>"
id="<?= $this->nameToId($currentFieldName) ?>"
value="<?= e($value) ?>"
<?= $field->getAttributes() ?>
/>
<?php endforeach ?>
<?php endforeach ?>
<?php endforeach ?>

View File

@@ -0,0 +1,20 @@
<div
data-control="formwidget"
data-refresh-handler="<?= $this->getEventHandler('onRefresh') ?>"
class="layout-row"
role="form"
id="<?= $this->getId($renderSection.'Container') ?>">
<?php if ($renderSection == 'outside'): ?>
<?= $this->makePartial('section', ['tabs' => $outsideTabs]) ?>
<?php endif ?>
<?php if ($renderSection == 'primary'): ?>
<?= $this->makePartial('section', ['tabs' => $primaryTabs]) ?>
<?php endif ?>
<?php if ($renderSection == 'secondary'): ?>
<?= $this->makePartial('section', ['tabs' => $secondaryTabs]) ?>
<?php endif ?>
</div>

View File

@@ -0,0 +1,30 @@
<?php
$type = $tabs->section;
$containerCss = 'layout-row min-size';
if ($tabs->stretch) {
$containerCss = 'layout-row';
}
?>
<!-- <?= ucfirst($type) ?> Tabs -->
<div class="<?= $containerCss ?>">
<?php if ($tabs->suppressTabs): ?>
<div
id="<?= $this->getId($type.'Tabs') ?>"
class="form-tabless-fields <?= $tabs->cssClass ?>">
<?= $this->makePartial('form_fields', ['fields' => $tabs]) ?>
</div>
<?php else: ?>
<div
id="<?= $this->getId($type.'Tabs') ?>"
class="control-tabs <?= $type ?>-tabs layout <?= $tabs->cssClass ?>"
data-control="tab">
<?= $this->makePartial('form_tabs', ['tabs' => $tabs]) ?>
</div>
<?php endif ?>
</div>

View File

@@ -0,0 +1,50 @@
/*
* Drag-and-drop reordering styles for the list widget.
*/
.control-list .list-sort-handle-column,
.control-list .list-cell-sort-handle {
width: 28px;
text-align: center;
}
/* The whole handle cell is the drag target (SortableJS handle is the cell), so
the grab affordance and hit area cover the entire cell, not just the icon.
The second selector is specific enough to override rowlink's
"tr.rowlink td.nolink { cursor: auto }" rule, which would otherwise reset the
cursor on this (intentionally) non-link cell when the list rows are clickable. */
.control-list .list-cell-sort-handle,
.control-list tr.rowlink td.list-cell-sort-handle.nolink {
cursor: move;
cursor: grab;
}
.control-list .list-cell-sort-handle:active,
.control-list tr.rowlink td.list-cell-sort-handle.nolink:active {
cursor: grabbing;
}
.control-list .list-sort-handle {
display: inline-block;
color: #666;
line-height: 1;
text-decoration: none;
cursor: inherit;
}
.control-list tr:hover .list-sort-handle {
color: #333;
}
.control-list .list-sortable-ghost {
opacity: 0.5;
background: #f0f7fd;
}
.control-list .list-sortable-chosen {
background: #f7f9fa;
}
.control-list[data-sortable="true"] tbody tr {
/* Avoid text selection while dragging rows */
user-select: none;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,99 @@
import Sortable from 'sortablejs';
/*
* List widget drag-and-drop reordering.
*
* Additive enhancement on top of the existing list widget. When a list is rendered with
* `data-sortable="true"`, SortableJS is initialised on its <tbody> using the per-row drag
* handle cell. On drop, the record ids in their new DOM order are posted to the list's reorder
* handler, which assigns the sort order values server-side (by position) and re-renders.
*/
(function ($) {
"use strict";
function collectIds(tbody) {
return Array.prototype.map.call(
tbody.querySelectorAll('tr[data-record-id]'),
function (tr) {
return tr.getAttribute('data-record-id');
}
);
}
function initList(listEl) {
var tbody = listEl.querySelector('tbody');
if (!tbody || tbody.wnListSortable) {
return;
}
var handler = listEl.getAttribute('data-reorder-handler');
if (!handler) {
return;
}
var $list = $(listEl);
// SortableJS dispatches a native "change" event on the list root (the tbody) while
// an item is being dragged. Stop it bubbling to the surrounding form's change monitor
// so reordering — which persists immediately — does not flag the form as having
// unsaved changes. Real form-field changes (target = input/select/textarea) are left
// untouched.
tbody.addEventListener('change', function (event) {
if (event.target === tbody) {
event.stopPropagation();
}
});
var reorderInFlight = false;
tbody.wnListSortable = Sortable.create(tbody, {
handle: '.list-cell-sort-handle',
draggable: 'tr',
filter: '.no-data',
animation: 150,
ghostClass: 'list-sortable-ghost',
chosenClass: 'list-sortable-chosen',
onEnd: function (event) {
// Nothing changed if the row was dropped back in its original position.
if (event.oldIndex === event.newIndex) {
return;
}
// Ignore further drops until the current reorder has been persisted and the
// list re-rendered, so overlapping requests can't race and persist a stale order.
if (reorderInFlight) {
return;
}
reorderInFlight = true;
// The request is fired programmatically (not from a [data-request] element),
// so show the stripe load indicator manually for feedback.
var indicator = ($.wn && $.wn.stripeLoadIndicator) || ($.oc && $.oc.stripeLoadIndicator);
if (indicator) {
indicator.show();
}
// Only the record ids (in their new order) are sent; the server assigns the
// sort order values by position.
$list.request(handler, {
data: { record_ids: collectIds(tbody) }
}).always(function () {
reorderInFlight = false;
if (indicator) {
indicator.hide();
}
});
}
});
}
function initAll() {
var lists = document.querySelectorAll('[data-control="listwidget"][data-sortable="true"]');
Array.prototype.forEach.call(lists, initList);
}
$(document).ready(initAll);
// Re-initialise after the list partial is replaced by an AJAX update (e.g. onRefresh).
$(document).on('render', initAll);
})(window.jQuery);

View File

@@ -0,0 +1,166 @@
/*
* List Widget
*
* Dependences:
* - Row Link Plugin (system/assets/ui/js/list.rowlink.js)
*/
+function ($) { "use strict";
var ListWidget = function (element, options) {
var $el = this.$el = $(element);
this.options = options || {};
var scrollClassContainer = options.scrollClassContainer !== undefined
? options.scrollClassContainer
: $el.parent()
$el.dragScroll({
scrollClassContainer: scrollClassContainer,
scrollSelector: 'thead',
dragSelector: 'thead'
})
this.update()
}
ListWidget.DEFAULTS = {
}
ListWidget.prototype.update = function() {
var
list = this.$el,
head = $('thead', list),
body = $('tbody', list),
foot = $('tfoot', list)
/*
* Bind check boxes
*/
$('.list-checkbox input[type="checkbox"]', body).each(function(){
var $el = $(this)
if ($el.is(':checked'))
$el.closest('tr').addClass('active')
})
head.on('change', '.list-checkbox input[type="checkbox"]', function(){
var $el = $(this),
checked = $el.is(':checked')
$('.list-checkbox input[type="checkbox"]', body).prop('checked', checked)
if (checked)
$('tr', body).addClass('active')
else
$('tr', body).removeClass('active')
})
body.on('change', '.list-checkbox input[type="checkbox"]', function(){
var $el = $(this),
checked = $el.is(':checked')
if (checked) {
$el.closest('tr').addClass('active')
}
else {
$('.list-checkbox input[type="checkbox"]', head).prop('checked', false)
$el.closest('tr').removeClass('active')
}
})
this.lastChecked = null
body.on('click', '.list-checkbox input[type="checkbox"]', (e) => {
const current = e.currentTarget
if (this.lastChecked && e.shiftKey) {
const checkboxes = $('.list-checkbox input[type="checkbox"]', body)
const start = checkboxes.index(current)
const end = checkboxes.index(this.lastChecked)
checkboxes
.slice(Math.min(start, end), Math.max(start, end) + 1)
.each(function () {
$(this).prop('checked', current.checked).trigger('change')
})
}
this.lastChecked = current
})
}
ListWidget.prototype.getChecked = function() {
var
list = this.$el,
body = $('tbody', list)
return $('.list-checkbox input[type="checkbox"]', body).map(function(){
var $el = $(this)
if ($el.is(':checked'))
return $el.val()
}).get();
}
ListWidget.prototype.toggleChecked = function(el) {
var $checkbox = $('.list-checkbox input[type="checkbox"]', $(el).closest('tr'))
$checkbox.prop('checked', !$checkbox.is(':checked')).trigger('change')
}
// LIST WIDGET PLUGIN DEFINITION
// ============================
var old = $.fn.listWidget
$.fn.listWidget = function (option) {
var args = Array.prototype.slice.call(arguments, 1), result
this.each(function () {
var $this = $(this)
var data = $this.data('oc.listwidget')
var options = $.extend({}, ListWidget.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.listwidget', (data = new ListWidget(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : this
}
$.fn.listWidget.Constructor = ListWidget
// LIST WIDGET NO CONFLICT
// =================
$.fn.listWidget.noConflict = function () {
$.fn.listWidget = old
return this
}
// LIST WIDGET HELPERS
// =================
if ($.wn === undefined)
$.wn = {}
if ($.oc === undefined)
$.oc = $.wn
$.wn.listToggleChecked = function(el) {
$(el)
.closest('[data-control="listwidget"]')
.listWidget('toggleChecked', el)
}
$.wn.listGetChecked = function(el) {
return $(el)
.closest('[data-control="listwidget"]')
.listWidget('getChecked')
}
// LIST WIDGET DATA-API
// ==============
$(document).render(function(){
$('[data-control="listwidget"]').listWidget();
})
}(window.jQuery);

View File

@@ -0,0 +1,3 @@
<div class="list-widget list-scrollable-container <?= $cssClasses ?>" id="<?= $this->getId() ?>">
<?= $this->makePartial('list') ?>
</div>

View File

@@ -0,0 +1,42 @@
<div class="control-list list-scrollable" data-control="listwidget"
<?php if ($sortable): ?>
data-sortable="true"
data-reorder-handler="<?= e($reorderHandler) ?>"
<?php endif ?>
>
<table class="table data" data-control="rowlink">
<thead>
<?php if ($showTotals): ?>
<?= $this->makePartial('list_totals') ?>
<?php endif; ?>
<?= $this->makePartial('list_head_row') ?>
</thead>
<tbody>
<?php if (count($records)): ?>
<?= $this->makePartial('list_body_rows') ?>
<?php else: ?>
<tr class="no-data">
<td colspan="<?= $columnTotal ?>" class="nolink">
<p class="no-data"><?= $noRecordsMessage ?></p>
</td>
</tr>
<?php endif ?>
</tbody>
<?php if ($showTotals): ?>
<tfoot>
<?= $this->makePartial('list_totals') ?>
</tfoot>
<?php endif; ?>
</table>
<?php if ($showPagination): ?>
<div class="list-footer">
<div class="list-pagination">
<?php if ($showPageNumbers): ?>
<?= $this->makePartial('list_pagination') ?>
<?php else: ?>
<?= $this->makePartial('list_pagination_simple') ?>
<?php endif ?>
</div>
</div>
<?php endif ?>
</div>

View File

@@ -0,0 +1,11 @@
<td class="list-checkbox nolink">
<div class="checkbox custom-checkbox nolabel">
<input
type="checkbox"
name="checked[]"
id="<?= $this->getId('checkbox-' . $record->getKey()) ?>"
value="<?= $record->getKey() ?>"
autocomplete="off"/>
<label for="<?= $this->getId('checkbox-' . $record->getKey()) ?>"><?= e(trans('backend::lang.list.check')) ?></label>
</div>
</td>

View File

@@ -0,0 +1,47 @@
<?php
$expanded = $showTree ? $this->isTreeNodeExpanded($record) : null;
$childRecords = $showTree ? $record->getChildren() : null;
$treeLevelClass = $showTree ? 'list-tree-level-'.$treeLevel : '';
?>
<tr class="<?= $treeLevelClass ?> <?= $this->getRowClass($record) ?>"
data-record-id="<?= e($record->getKey()) ?>"
>
<?php if ($showCheckboxes): ?>
<?= $this->makePartial('list_body_checkbox', ['record' => $record]) ?>
<?php endif ?>
<?php if ($showTree): ?>
<?= $this->makePartial('list_body_tree', [
'record' => $record,
'expanded' => $expanded,
'childCount' => $record->getChildCount()
]) ?>
<?php endif ?>
<?php if (!empty($sortable)): ?>
<td class="list-cell-sort-handle nolink">
<span class="list-sort-handle" title="<?= e(trans('backend::lang.list.sort_drag_title')) ?>"><i class="icon-bars"></i></span>
</td>
<?php endif ?>
<?php $index = $url = 0; foreach ($columns as $key => $column): ?>
<?php $index++; ?>
<td class="list-cell-index-<?= $index ?> list-cell-name-<?= $column->getName() ?> list-cell-type-<?= $column->type ?> <?= $column->clickable ? '' : 'nolink' ?> <?= $column->getAlignClass() ?> <?= $column->cssClass ?>">
<?php if ($column->clickable && !$url && ($url = $this->getRecordUrl($record))): ?>
<a <?= $this->getRecordOnClick($record) ?> href="<?= $url ?>">
<?= $this->getColumnValue($record, $column) ?>
</a>
<?php else: ?>
<?= $this->getColumnValue($record, $column) ?>
<?php endif ?>
</td>
<?php endforeach ?>
<?php if ($showSetup): ?>
<td class="list-setup">&nbsp;</td>
<?php endif ?>
</tr>
<?php if ($showTree && $expanded): ?>
<?= $this->makePartial('list_body_rows', ['records' => $childRecords, 'treeLevel' => $treeLevel+1]) ?>
<?php endif ?>

View File

@@ -0,0 +1,3 @@
<?php foreach ($records as $record): ?>
<?= $this->makePartial('list_body_row', ['record' => $record, 'treeLevel' => $treeLevel]) ?>
<?php endforeach ?>

View File

@@ -0,0 +1,16 @@
<td class="list-tree nolink">
<a
href="javascript:;"
class="list-expand-collapse"
data-request="<?= $this->getEventHandler('onToggleTreeNode') ?>"
data-stripe-load-indicator
data-request-data="node_id: '<?= $record->getKey() ?>', status: <?= $expanded ? 1 : 0 ?>">
<?php if (!$childCount): ?>
<i class="icon-square-o"></i>
<?php elseif ($expanded): ?>
<i class="icon-minus-square-o"></i>
<?php else: ?>
<i class="icon-plus-square-o"></i>
<?php endif ?>
</a>
</td>

View File

@@ -0,0 +1,58 @@
<tr>
<?php if ($showCheckboxes && count($records)): ?>
<th class="list-checkbox">
<div class="checkbox custom-checkbox nolabel">
<input type="checkbox" id="<?= $this->getId('checkboxAll') ?>" />
<label for="<?= $this->getId('checkboxAll') ?>"></label>
</div>
</th>
<?php endif ?>
<?php if ($showTree): ?>
<th class="list-tree">
<span></span>
</th>
<?php endif ?>
<?php if (!empty($sortable)): ?>
<th class="list-sort-handle-column"><span></span></th>
<?php endif ?>
<?php foreach ($columns as $key => $column): ?>
<?php if ($showSorting && $column->sortable): ?>
<th
<?php if ($column->width): ?>
style="width: <?= $column->width ?>"
<?php endif ?>
class="sortable <?= $this->sortColumn==$column->columnName?'sort-'.$this->sortDirection.' active':'' ?> list-cell-name-<?= $column->getName() ?> list-cell-type-<?= $column->type ?> <?= $column->getAlignClass() ?> <?= $column->headCssClass ?>"
>
<a
href="javascript:;"
data-request="<?= $this->getEventHandler('onSort') ?>"
data-stripe-load-indicator
data-request-data="sortColumn: '<?= $column->columnName ?>', page: <?= $pageCurrent ?>">
<?= $this->getHeaderValue($column) ?>
</a>
</th>
<?php else: ?>
<th
<?php if ($column->width): ?>
style="width: <?= $column->width ?>"
<?php endif ?>
class="list-cell-name-<?= $column->getName() ?> list-cell-type-<?= $column->type ?> <?= $column->getAlignClass() ?> <?= $column->headCssClass ?>"
>
<span><?= $this->getHeaderValue($column) ?></span>
</th>
<?php endif ?>
<?php endforeach ?>
<?php if ($showSetup): ?>
<th class="list-setup">
<a href="javascript:;"
id="<?= $this->getId('setupButton') ?>"
title="<?= e(trans('backend::lang.list.setup_title')) ?>"
data-control="popup"
data-handler="<?= $this->getEventHandler('onLoadSetup') ?>"></a>
</th>
<?php endif ?>
</tr>

View File

@@ -0,0 +1,74 @@
<div class="loading-indicator-container size-small pull-right">
<div class="control-pagination loading-indicator-container">
<span class="page-iteration">
<?= e(trans('backend::lang.list.pagination', ['from' => $pageFrom, 'to' => $pageTo, 'total' => $recordTotal])) ?>
</span>
<?php if ($pageLast > 1): ?>
<?php if ($pageCurrent > 1): ?>
<a
href="javascript:;"
class="page-first"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-request-data="page: 1"
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
title="<?= e(trans('backend::lang.list.first_page')) ?>"></a>
<?php else: ?>
<span
class="page-first"
title="<?= e(trans('backend::lang.list.first_page')) ?>"></span>
<?php endif ?>
<?php if ($pageCurrent > 1): ?>
<a
href="javascript:;"
class="page-back"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-request-data="page: <?= $pageCurrent-1 ?>"
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
title="<?= e(trans('backend::lang.list.prev_page')) ?>"></a>
<?php else: ?>
<span
class="page-back"
title="<?= e(trans('backend::lang.list.prev_page')) ?>"></span>
<?php endif ?>
<input
type="number"
name="page"
value="<?= $pageCurrent ?>"
min="1"
step="1"
max="<?= $pageLast ?>"
class="form-control input-sm"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-track-input
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
autocomplete="off"
style="width: auto; padding-left: 5px; padding-right: 0; display: inline; text-align: center;" />
<?php if ($pageLast > $pageCurrent): ?>
<a
href="javascript:;"
class="page-next"
data-request-data="page: <?= $pageCurrent+1 ?>"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
title="<?= e(trans('backend::lang.list.next_page')) ?>"></a>
<?php else: ?>
<span
class="page-next"
title="<?= e(trans('backend::lang.list.next_page')) ?>"></span>
<?php endif ?>
<?php if ($pageLast > $pageCurrent): ?>
<a
href="javascript:;"
class="page-last"
data-request-data="page: <?= $pageLast ?>"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
title="<?= e(trans('backend::lang.list.last_page')) ?>"></a>
<?php else: ?>
<span
class="page-last"
title="<?= e(trans('backend::lang.list.last_page')) ?>"></span>
<?php endif ?>
<?php endif ?>
</div>
</div>

View File

@@ -0,0 +1,50 @@
<div class="loading-indicator-container size-small pull-right">
<div class="control-pagination">
<?php if ($pageCurrent > 1): ?>
<a
href="javascript:;"
class="page-first"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-request-data="page: 1"
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
title="<?= e(trans('backend::lang.list.first_page')) ?>"></a>
<?php else: ?>
<span
class="page-first"
title="<?= e(trans('backend::lang.list.first_page')) ?>"></span>
<?php endif ?>
<?php if ($pageCurrent > 1): ?>
<a
href="javascript:;"
class="page-back"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-request-data="page: <?= $pageCurrent-1 ?>"
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
title="<?= e(trans('backend::lang.list.prev_page')) ?>"></a>
<?php else: ?>
<span
class="page-back"
title="<?= e(trans('backend::lang.list.prev_page')) ?>"></span>
<?php endif ?>
<select
disabled
name="page"
class="form-control input-sm custom-select select-no-search"
autocomplete="off">
<option value="<?= $pageCurrent ?>" selected><?= $pageCurrent ?></option>
</select>
<?php if ($hasMorePages): ?>
<a
href="javascript:;"
class="page-next"
data-request-data="page: <?= $pageCurrent+1 ?>"
data-request="<?= $this->getEventHandler('onPaginate') ?>"
data-load-indicator="<?= e(trans('backend::lang.list.loading')) ?>"
title="<?= e(trans('backend::lang.list.next_page')) ?>"></a>
<?php else: ?>
<span
class="page-next"
title="<?= e(trans('backend::lang.list.next_page')) ?>"></span>
<?php endif ?>
</div>
</div>

View File

@@ -0,0 +1,26 @@
<tr class="table-totals">
<?php if ($showCheckboxes): ?>
<td></td>
<?php endif ?>
<?php if ($showTree): ?>
<td class="list-tree">
<span></span>
</td>
<?php endif ?>
<?php foreach ($columns as $column): ?>
<td>
<?php if ($column->type == 'number' && $column->summable): ?>
<?php $item = $sums[$column->columnName]; ?>
<span>
<?= $item['format'] ? sprintf($item['format'], $item['sum']) : number_format($item['sum'], 0, '.', ',') ?>
<?php if (!is_null($item['total'])): ?>
(<?= $item['format'] ? sprintf($item['format'], $item['total']) : number_format($item['total'], 0, '.', ',') ?>)
<?php endif; ?>
</span>
<?php endif ?>
</td>
<?php endforeach ?>
<?php if ($showSetup): ?>
<td></td>
<?php endif; ?>
</tr>

View File

@@ -0,0 +1,74 @@
<?= Form::open(['data-request-parent' => '#' . $this->getId('setupButton')]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.list.setup_title')) ?></h4>
</div>
<div class="modal-body">
<p class="help-block before-field"><?= e(trans('backend::lang.list.setup_help')) ?></p>
<div class="control-simplelist with-checkboxes is-sortable" data-control="simplelist">
<ul>
<?php foreach ($columns as $key => $column): ?>
<li>
<div class="checkbox custom-checkbox">
<input
type="hidden"
name="column_order[]"
value="<?= e($column->columnName) ?>" />
<input
id="<?= $this->getId('setupCheckbox-'.$column->columnName) ?>"
name="visible_columns[]"
value="<?= e($column->columnName) ?>"
<?= $column->invisible ? '' : 'checked="checked"' ?>
type="checkbox" />
<label
class="choice"
for="<?= $this->getId('setupCheckbox-'.$column->columnName) ?>">
<?= e(trans($column->label)) ?>
</label>
</div>
</li>
<?php endforeach ?>
</ul>
</div>
<?php if ($this->showPagination): ?>
<div class="form-group">
<label><?= e(trans('backend::lang.list.records_per_page')) ?></label>
<p class="help-block before-field">
<?= e(trans('backend::lang.list.records_per_page_help')) ?>
</p>
<select class="form-control custom-select select-no-search" name="records_per_page">
<?php foreach ($perPageOptions as $optionValue): ?>
<option value="<?= $optionValue ?>" <?= $optionValue == $recordsPerPage ? 'selected="selected"' : '' ?>><?= $optionValue ?></option>
<?php endforeach ?>
</select>
</div>
<?php endif ?>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-link pull-left"
data-request="<?= $this->getEventHandler('onResetSetup') ?>"
data-dismiss="popup"
data-stripe-load-indicator>
<?= e(trans('backend::lang.form.reset_default')) ?>
</button>
<button
type="button"
class="btn btn-primary"
data-request="<?= $this->getEventHandler('onApplySetup') ?>"
data-dismiss="popup"
data-stripe-load-indicator>
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@@ -0,0 +1,169 @@
div[data-control="media-manager"]:focus{outline:none}
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"] .file-icon{fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;display:inline-block}
div[data-control="media-manager"] .file-icon-extension{font-family:'ArialMT','Arial',sans-serif;font-size:4em;font-weight:900;fill:#fff}
div[data-control="media-manager"] .file-icon-label{fill:#576D7E;fill-rule:nonzero}
div[data-control="media-manager"] .file-icon-css,
div[data-control="media-manager"] .file-icon-less,
div[data-control="media-manager"] .file-icon-scss{fill:#B73FD9}
div[data-control="media-manager"] .file-icon-html,
div[data-control="media-manager"] .file-icon-xml{fill:#EA9B47}
div[data-control="media-manager"] .file-icon-js,
div[data-control="media-manager"] .file-icon-json{fill:#A9A9A9}
div[data-control="media-manager"] .file-icon-pdf{fill:#E30713}
div[data-control="media-manager"] .file-icon-txt{fill:#248BD0}
div[data-control="media-manager"] .file-icon-ai{fill:#F29200}
div[data-control="media-manager"] .file-icon-eps{fill:#F9B234}
div[data-control="media-manager"] .file-icon-psd{fill:#2DAAE2}
div[data-control="media-manager"] .file-icon-ttf,
div[data-control="media-manager"] .file-icon-otf,
div[data-control="media-manager"] .file-icon-woff,
div[data-control="media-manager"] .file-icon-woff2{fill:#C4CA10}
div[data-control="media-manager"] .file-icon-doc,
div[data-control="media-manager"] .file-icon-docx,
div[data-control="media-manager"] .file-icon-rtf,
div[data-control="media-manager"] .file-icon-odt{fill:#0F70B7}
div[data-control="media-manager"] .file-icon-csv,
div[data-control="media-manager"] .file-icon-ods,
div[data-control="media-manager"] .file-icon-xls,
div[data-control="media-manager"] .file-icon-xlsx{fill:#3BAA34}
div[data-control="media-manager"] .file-icon-odp,
div[data-control="media-manager"] .file-icon-ppt,
div[data-control="media-manager"] .file-icon-pptx{fill:#D04526}
div[data-control="media-manager"] .file-icon-rar,
div[data-control="media-manager"] .file-icon-tar,
div[data-control="media-manager"] .file-icon-zip{fill:#363A56}
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"] .empty-library{padding:20px;text-align:center}
div[data-control="media-manager"] p.thumbnail-error-message{font-size:12px;margin:10px;line-height:160%;color:#bdc3c7}
div[data-control="media-manager"] .media-list{padding:0 0 0 20px;margin:0;-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:focus{outline:none}
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;line-height:150%;margin:15px 0 5px 0;padding-right:0;-webkit-transition:padding 0.1s;transition:padding 0.1s;position:relative}
div[data-control="media-manager"] .media-list li h4 a{position:absolute;right:0;top:0;font-size:15px;color:#2b3e50;display:none}
div[data-control="media-manager"] .media-list li h4 a:hover{color:#2da7c7;text-decoration:none}
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/system/assets/ui/images/loader-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:#48b2ce}
div[data-control="media-manager"] .media-list.list li{height:75px;width:260px;border:1px solid #ecf0f1;background:#f6f8f9;box-sizing:content-box}
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 img{max-height:75px}
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 svg{max-height:44px}
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:#48b2ce !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:#48b2ce !important}
div[data-control="media-manager"] .media-list.list h4{padding-right:15px}
div[data-control="media-manager"] .media-list.list h4 a{right:15px}
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/system/assets/ui/images/loader-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:165px;height:165px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border:1px solid #ecf0f1;overflow:hidden;background:#f6f8f9;box-sizing:content-box}
div[data-control="media-manager"] .media-list.tiles li .icon-container img{max-height:165px}
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 svg{max-height:65px}
div[data-control="media-manager"] .media-list.tiles li .icon-container p{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"}
div[data-control="media-manager"] .media-list.tiles li.selected .icon-container{background:#48b2ce !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"] [data-control="sidebar-labels"]{word-wrap:break-word}
div[data-control="media-manager"] .sidebar-group{margin-bottom:20px}
div[data-control="media-manager"] .sidebar-image-placeholder-container,
div[data-control="media-manager"] .sidebar-document-placeholder-container{display:table;width:100%}
div[data-control="media-manager"] .sidebar-image-placeholder,
div[data-control="media-manager"] .sidebar-document-placeholder{display:table-cell;position:relative;vertical-align:middle;text-align:center;border-bottom:1px solid #ecf0f1;box-sizing:content-box}
div[data-control="media-manager"] .sidebar-image-placeholder{height:225px}
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/system/assets/ui/images/loader-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,
div[data-control="media-manager"] .sidebar-image-placeholder i.icon-level-up{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:10px;line-height:160%;color:#bdc3c7;margin-top:25px}
div[data-control="media-manager"] .sidebar-image-placeholder img{max-width:100%;max-height:225px}
div[data-control="media-manager"] .sidebar-document-placeholder{height:155px}
div[data-control="media-manager"] .sidebar-document-placeholder svg{width:100px;height:100px}
div[data-control="media-manager"] .list-container{position:relative;z-index:100}
div[data-control="media-manager"] .list-container .no-data{font-size:13px}
div[data-control="media-manager"] .list-container p.no-data{padding:0 20px 20px 20px}
div[data-control="media-manager"] .list-container li.no-data{padding-top:20px;display:block !important;width:100% !important;border:none !important;background:transparent !important;cursor:default !important}
div[data-control="media-manager"] .list-container table.table.data tbody tr:not(.no-data):active td{background:#48b2ce !important}
div[data-control="media-manager"] [data-control="item-list"]{position:relative;display:table-cell}
div[data-control="media-manager"] .control-scrollpad{position:absolute;left:0;top:0;min-height:300px}
div[data-control="media-manager"] .scroll-wrapper{position:relative}
div[data-control="media-manager"] table.table{table-layout:fixed;margin-bottom:0;white-space:nowrap}
div[data-control="media-manager"] table.table div.no-wrap-text{overflow:hidden;text-overflow:ellipsis}
div[data-control="media-manager"] table.table div.item-title{position:relative;padding-right:0;-webkit-transition:padding 0.1s;transition:padding 0.1s}
div[data-control="media-manager"] table.table div.item-title a{position:absolute;right:0;top:0;display:none}
div[data-control="media-manager"] table.table tr:hover div.item-title{padding-right:25px}
div[data-control="media-manager"] table.table tr:hover div.item-title a{display:block}
div[data-control="media-manager"] table.table tr[data-item-type=folder] i.icon-folder{color:#48b2ce}
div[data-control="media-manager"] table.table tr:focus{outline:none}
div[data-control="media-manager"] div[data-control="selection-marker"]{position:absolute;z-index:250;border:1px dashed #95a5a6;background:rgba(0,0,0,0.1)}
div[data-control="media-manager"] .upload-progress{background:#f9f9f9;padding:0 20px}
div[data-control="media-manager"] .upload-progress h5{margin:0 0 10px 0;font-size:13px;color:#2b3e50;font-weight:600}
div[data-control="media-manager"] .upload-progress h5 span{display:inline-block;margin-left:10px;color:#95a5a6;font-size:15px}
div[data-control="media-manager"] .upload-progress .progress-controls{padding-right:30px;position:relative}
div[data-control="media-manager"] .upload-progress .progress-controls .controls{position:absolute;right:0;bottom:0}
div[data-control="media-manager"] .upload-progress .progress-controls .controls a{display:block;position:relative;top:7px;right:3px;color:#95a5a6;font-size:16px;cursor:pointer!important}
div[data-control="media-manager"] .upload-progress .progress-controls .controls a:hover{text-decoration:none;color:#2da7c7}
div[data-control="media-manager"] .dz-preview{display:none}
div[data-control="media-manager"] button[data-command="toggle-sidebar"].sidebar-hidden{-webkit-transform:rotate(180deg) translate(0,0);-ms-transform:rotate(180deg) translate(0,0);transform:rotate(180deg) translate(0,0)}
[data-control="media-manager-crop-tool"] .image_area{position:absolute;width:100%;height:100%;overflow:auto}
[data-control="media-manager-crop-tool"] .image_area .jcrop-holder{background-color:transparent!important}
[data-control="media-manager-crop-tool"] img{cursor:crosshair;display:block}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container .layout-relative{overflow:hidden}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.horizontal .layout-cell{height:20px}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.horizontal .layout-relative{width:100%}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.vertical{width:20px}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.vertical .layout-relative{height:100%}
[data-control="media-manager-crop-tool"].has-rulers .ruler{position:absolute;height:20px;margin-left:-3px;background:#555}
[data-control="media-manager-crop-tool"].has-rulers .ruler ul{margin:0;padding:0;white-space:nowrap;font-size:0}
[data-control="media-manager-crop-tool"].has-rulers .ruler li{margin:0;padding:0 0 0 40px;list-style:none;display:inline-block;width:24px;margin:0 -10px 0 -14px;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;text-align:left;position:relative;font-size:10px;line-height:20px;color:#ecf0f1;font-family:Arial,sans-serif}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:before,
[data-control="media-manager-crop-tool"].has-rulers .ruler li:after{content:' ';position:absolute;border-left:1px solid #8e8e8e}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:before{height:20px;top:0;left:-3px}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:after{height:3px;bottom:0;left:20px}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:first-child:after{display:none}
[data-control="media-manager-crop-tool"].has-rulers .ruler[data-control=v-ruler]{-webkit-transform:rotateZ(90deg);-ms-transform:rotateZ(90deg);transform:rotateZ(90deg);-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;transform-origin:left top;left:23px;top:-23px}
[data-control="media-manager-crop-tool"].has-rulers .ruler[data-control=v-ruler] li:after{top:0;left:auto}
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover .icon-container{background:#48b2ce !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.tiles li:hover h4{padding-right:20px !important}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover{background:#48b2ce !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:#48b2ce !important}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover h4{padding-right:35px !important}
body:not(.no-select) div[data-control="media-manager"] .media-list li:hover h4 a{display:block}
@media (max-width:1280px){div[data-control="media-manager"] .media-list.list li{width:230px}}
@media (max-width:1024px){div[data-control="media-manager"] .media-list.list li{display:block;width:auto}}
@media (max-width:768px){div[data-control="media-manager"] [data-control="preview-sidebar"],div[data-control="media-manager"] [data-command="toggle-sidebar"]{display:none!important}div[data-control="media-manager"] .media-list.list{padding:0}div[data-control="media-manager"] .media-list.list li{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:0;border-right:none;border-left:none;border-bottom:none}}
@media (max-width:480px){div[data-control="media-manager"] [data-control="left-sidebar"]{display:none!important}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,543 @@
+function($){"use strict";if($.wn.mediaManager===undefined)$.wn.mediaManager={}
var Base=$.wn.foundation.base,BaseProto=Base.prototype
var MediaManager=function(element,options){this.$el=$(element)
this.$form=this.$el.closest('form')
this.options=options
Base.call(this)
this.selectTimer=null
this.sidebarPreviewElement=null
this.itemListElement=null
this.scrollContentElement=null
this.thumbnailQueue=[]
this.activeThumbnailQueueLength=0
this.sidebarThumbnailAjax=null
this.selectionMarker=null
this.dropzone=null
this.searchTrackInputTimer=null
this.navigationAjax=null
this.dblTouchTimer=null
this.dblTouchFlag=null
this.itemListPosition=null
this.init()}
MediaManager.prototype=Object.create(BaseProto)
MediaManager.prototype.constructor=MediaManager
MediaManager.prototype.dispose=function(){this.unregisterHandlers()
this.clearSelectTimer()
this.destroyUploader()
this.clearSearchTrackInputTimer()
this.releaseNavigationAjax()
this.clearDblTouchTimer()
this.removeAttachedControls()
this.removeScroll()
this.$el.removeData('oc.mediaManager')
this.$el=null
this.$form=null
this.sidebarPreviewElement=null
this.itemListElement=null
this.scrollContentElement=null
this.sidebarThumbnailAjax=null
this.selectionMarker=null
this.thumbnailQueue=[]
this.navigationAjax=null
BaseProto.dispose.call(this)}
MediaManager.prototype.getSelectedItems=function(returnNotProcessed,allowRootItem){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'),result=[]
if(!allowRootItem){var filteredItems=[]
for(var i=0,len=items.length;i<len;i++){var item=items[i]
if(!item.hasAttribute('data-root'))filteredItems.push(item)}items=filteredItems}if(returnNotProcessed===true)return items
for(var i=0,len=items.length;i<len;i++){var item=items[i],itemDetails={itemType:item.getAttribute('data-item-type'),path:item.getAttribute('data-path'),title:item.getAttribute('data-title'),sizeBytes:item.getAttribute('data-size-bytes'),lastModified:item.getAttribute('data-last-modified-ts'),documentType:item.getAttribute('data-document-type'),folder:item.getAttribute('data-folder'),publicUrl:item.getAttribute('data-public-url')}
result.push(itemDetails)}return result}
MediaManager.prototype.init=function(){this.itemListElement=this.$el.find('[data-control="item-list"]').get(0)
this.scrollContentElement=this.itemListElement.querySelector('.scroll-wrapper')
if(this.options.bottomToolbar){this.$el.find('[data-control="bottom-toolbar"]').removeClass('hide')
if(this.options.cropAndInsertButton)this.$el.find('[data-popup-command="crop-and-insert"]').removeClass('hide')}this.registerHandlers()
this.updateSidebarPreview()
this.generateThumbnails()
this.initUploader()
this.initScroll()}
MediaManager.prototype.registerHandlers=function(){this.$el.on('dblclick',this.proxy(this.onNavigate))
this.$el.on('click.tree-path','ul.tree-path, [data-control="sidebar-labels"]',this.proxy(this.onNavigate))
this.$el.on('click.command','[data-command]',this.proxy(this.onCommandClick))
this.$el.on('click.item','[data-type="media-item"]',this.proxy(this.onItemClick))
this.$el.on('touchend','[data-type="media-item"]',this.proxy(this.onItemTouch))
this.$el.on('change','[data-control="sorting"]',this.proxy(this.onSortingChanged))
this.$el.on('input','[data-control="search"]',this.proxy(this.onSearchChanged))
this.$el.on('mediarefresh',this.proxy(this.refresh))
this.$el.on('shown.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupShown))
this.$el.on('hidden.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupHidden))
this.$el.on('shown.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupShown))
this.$el.on('hidden.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupHidden))
this.$el.on('keydown',this.proxy(this.onKeyDown))
if(this.itemListElement)this.itemListElement.addEventListener('mousedown',this.proxy(this.onListMouseDown))}
MediaManager.prototype.unregisterHandlers=function(){this.$el.off('dblclick',this.proxy(this.onNavigate))
this.$el.off('click.tree-path',this.proxy(this.onNavigate))
this.$el.off('click.command',this.proxy(this.onCommandClick))
this.$el.off('click.item',this.proxy(this.onItemClick))
this.$el.off('touchend','[data-type="media-item"]',this.proxy(this.onItemTouch))
this.$el.off('change','[data-control="sorting"]',this.proxy(this.onSortingChanged))
this.$el.off('keyup','[data-control="search"]',this.proxy(this.onSearchChanged))
this.$el.off('shown.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupShown))
this.$el.off('hidden.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupHidden))
this.$el.off('shown.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupShown))
this.$el.off('hidden.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupHidden))
this.$el.off('keydown',this.proxy(this.onKeyDown))
if(this.itemListElement){this.itemListElement.removeEventListener('mousedown',this.proxy(this.onListMouseDown))
this.itemListElement.removeEventListener('mousemove',this.proxy(this.onListMouseMove))}document.removeEventListener('mouseup',this.proxy(this.onListMouseUp))}
MediaManager.prototype.changeView=function(view){var data={view:view,path:this.$el.find('[data-type="current-folder"]').val()}
this.execNavigationRequest('onChangeView',data)}
MediaManager.prototype.setFilter=function(filter){var data={filter:filter,path:this.$el.find('[data-type="current-folder"]').val()}
this.execNavigationRequest('onSetFilter',data)}
MediaManager.prototype.isSearchMode=function(){return this.$el.find('[data-type="search-mode"]').val()=='true'}
MediaManager.prototype.initScroll=function(){this.$el.find('.control-scrollpad').scrollpad()}
MediaManager.prototype.updateScroll=function(){this.$el.find('.control-scrollpad').scrollpad('update')}
MediaManager.prototype.removeScroll=function(){this.$el.find('.control-scrollpad').scrollpad('dispose')}
MediaManager.prototype.scrollToTop=function(){this.$el.find('.control-scrollpad').scrollpad('scrollToStart')}
MediaManager.prototype.removeAttachedControls=function(){this.$el.find('[data-control=toolbar]').toolbar('dispose')
this.$el.find('[data-control=sorting]').select2('destroy')}
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')
for(var i=0,len=items.length;i<len;i++){items[i].setAttribute('class','')}node.setAttribute('class','selected')}else{if(node.getAttribute('class')=='selected')node.setAttribute('class','')
else node.setAttribute('class','selected')}node.focus()
this.clearSelectTimer()
if(this.isPreviewSidebarVisible()){this.selectTimer=setTimeout(this.proxy(this.updateSidebarPreview),100)}if(node.hasAttribute('data-root')&&!expandSelection){this.toggleMoveAndDelete(true)}else{this.toggleMoveAndDelete(false)}if(expandSelection){this.unselectRoot()}}
MediaManager.prototype.toggleMoveAndDelete=function(value){$('[data-command=delete]',this.$el).prop('disabled',value)
$('[data-command=move]',this.$el).prop('disabled',value)}
MediaManager.prototype.unselectRoot=function(){var rootItem=this.$el.get(0).querySelector('[data-type="media-item"][data-root].selected')
if(rootItem)rootItem.setAttribute('class','')}
MediaManager.prototype.clearDblTouchTimer=function(){if(this.dblTouchTimer===null)return
clearTimeout(this.dblTouchTimer)
this.dblTouchTimer=null}
MediaManager.prototype.clearDblTouchFlag=function(){this.dblTouchFlag=false}
MediaManager.prototype.selectFirstItem=function(){var firstItem=this.itemListElement.querySelector('[data-type="media-item"]:first-child')
if(firstItem){this.selectItem(firstItem)}}
MediaManager.prototype.selectRelative=function(next,expandSelection){var currentSelection=this.getSelectedItems(true,true)
if(currentSelection.length==0){this.selectFirstItem()
return}var itemToSelect=null
if(next){var lastItem=currentSelection[currentSelection.length-1]
if(lastItem)itemToSelect=lastItem.nextElementSibling}else{var firstItem=currentSelection[0]
if(firstItem)itemToSelect=firstItem.previousElementSibling}if(itemToSelect)this.selectItem(itemToSelect,expandSelection)}
MediaManager.prototype.gotoFolder=function(path,resetSearch){var data={path:path,resetSearch:resetSearch!==undefined?1:0}
this.execNavigationRequest('onGoToFolder',data)}
MediaManager.prototype.afterNavigate=function(){this.scrollToTop()
this.generateThumbnails()
this.updateSidebarPreview(true)
this.selectFirstItem()
this.updateScroll()}
MediaManager.prototype.refresh=function(){var data={path:this.$el.find('[data-type="current-folder"]').val(),clearCache:true}
this.execNavigationRequest('onGoToFolder',data)}
MediaManager.prototype.execNavigationRequest=function(handler,data,element){if(element===undefined)element=this.$form
if(this.navigationAjax!==null){try{this.navigationAjax.abort()}catch(e){}this.releaseNavigationAjax()}$.wn.stripeLoadIndicator.show()
this.navigationAjax=element.request(this.options.alias+'::'+handler,{data:data}).always(function(){$.wn.stripeLoadIndicator.hide()}).done(this.proxy(this.afterNavigate)).always(this.proxy(this.releaseNavigationAjax))}
MediaManager.prototype.releaseNavigationAjax=function(){this.navigationAjax=null}
MediaManager.prototype.navigateToItem=function($item){if(!$item.length||!$item.data('path').length)return
if($item.data('item-type')=='folder'){if(!$item.data('clear-search'))this.gotoFolder($item.data('path'))
else{this.resetSearch()
this.gotoFolder($item.data('path'),true)}}else if($item.data('item-type')=='file'){this.$el.trigger('popupcommand',['insert'])}}
MediaManager.prototype.isPreviewSidebarVisible=function(){return!this.$el.find('[data-control="preview-sidebar"]').hasClass('hide')}
MediaManager.prototype.toggleSidebar=function(ev){var isVisible=this.isPreviewSidebarVisible(),$sidebar=this.$el.find('[data-control="preview-sidebar"]'),$button=$(ev.target)
if(!isVisible){$sidebar.removeClass('hide')
this.updateSidebarPreview()
$button.removeClass('sidebar-hidden')}else{$sidebar.addClass('hide')
$button.addClass('sidebar-hidden')}this.$form.request(this.options.alias+'::onSetSidebarVisible',{data:{visible:(isVisible?0:1)}})}
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&&!items[0].hasAttribute('data-root')){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;case'document':template=previewPanel.querySelector('[data-control="document-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=='document')this.loadSidebarDocumentIcon(item)
if(documentType=='image')this.loadSidebarThumbnail()}else if(items.length==1&&items[0].hasAttribute('data-root')){template=previewPanel.querySelector('[data-control="go-up"]').innerHTML
previewContainer.innerHTML=template}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){this.sidebarPreviewElement.querySelector('[data-control="sidebar-labels"]').setAttribute('class','hide')}else if(items.length==1&&!items[0].hasAttribute('data-root')){this.sidebarPreviewElement.querySelector('[data-control="sidebar-labels"]').setAttribute('class','panel')
var item=items[0],lastModified=item.getAttribute('data-last-modified')
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=lastModified
previewPanel.querySelector('[data-label="public-url"]').setAttribute('href',item.getAttribute('data-public-url'))
if(lastModified)previewPanel.querySelector('[data-control="last-modified"]').setAttribute('class','')
else previewPanel.querySelector('[data-control="last-modified"]').setAttribute('class','hide')
if(this.isSearchMode()){previewPanel.querySelector('[data-control="item-folder"]').setAttribute('class','')
var folderNode=previewPanel.querySelector('[data-label="folder"]')
folderNode.textContent=item.getAttribute('data-folder')
folderNode.setAttribute('data-path',item.getAttribute('data-folder'))}else{previewPanel.querySelector('[data-control="item-folder"]').setAttribute('class','hide')}}else{this.sidebarPreviewElement.querySelector('[data-control="sidebar-labels"]').setAttribute('class','hide')}this.updateSidebarMediaPreview(items)}
MediaManager.prototype.loadSidebarDocumentIcon=function(item){var sidebarDocument=this.sidebarPreviewElement.querySelector('[data-control="sidebar-document"]'),svg=item.querySelector('svg')
sidebarDocument.innerHTML=svg.outerHTML}
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.proxy(this.replaceSidebarPlaceholder)).always(this.proxy(this.releaseSidebarThumbnailAjax))}
MediaManager.prototype.replaceSidebarPlaceholder=function(response){if(!this.sidebarPreviewElement)return
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}
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.proxy(this.placeholdersUpdated))}}
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.proxy(this.replacePlaceholder))
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()}
MediaManager.prototype.getRelativePosition=function(element,pageX,pageY,startPosition){var absolutePosition=startPosition!==undefined?startPosition:$.wn.foundation.element.absolutePosition(element,true)
return{x:(pageX-absolutePosition.left),y:(pageY-absolutePosition.top+this.scrollContentElement.scrollTop)}}
MediaManager.prototype.createSelectionMarker=function(){if(this.selectionMarker)return
this.selectionMarker=document.createElement('div')
this.selectionMarker.setAttribute('data-control','selection-marker')
this.scrollContentElement.insertBefore(this.selectionMarker,this.scrollContentElement.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)))}
MediaManager.prototype.initUploader=function(){if(!this.itemListElement||this.options.readOnly)return
var uploaderOptions={clickable:this.$el.find('[data-control="upload"]').get(0),url:this.options.url,paramName:'file_data',timeout:0,headers:{},createImageThumbnails:false}
var token=$('meta[name="csrf-token"]').attr('content')
if(token){uploaderOptions.headers['X-CSRF-TOKEN']=token}this.dropzone=new Dropzone(this.$el.get(0),uploaderOptions)
this.dropzone.on('addedfile',this.proxy(this.uploadFileAdded))
this.dropzone.on('totaluploadprogress',this.proxy(this.uploadUpdateTotalProgress))
this.dropzone.on('queuecomplete',this.proxy(this.uploadQueueComplete))
this.dropzone.on('sending',this.proxy(this.uploadSending))
this.dropzone.on('error',this.proxy(this.uploadError))
this.dropzone.on('success',this.proxy(this.uploadSuccess))
Snowboard.globalEvent("widgets.mediamanager.initUploader",this);}
MediaManager.prototype.destroyUploader=function(){if(!this.dropzone)return
this.dropzone.destroy()
this.dropzone=null}
MediaManager.prototype.uploadFileAdded=function(){this.showUploadUi()
this.setUploadProgress(0)
this.$el.find('[data-command="cancel-uploading"]').removeClass('hide')
this.$el.find('[data-command="close-uploader"]').addClass('hide')}
MediaManager.prototype.showUploadUi=function(){this.$el.find('[data-control="upload-ui"]').removeClass('hide')}
MediaManager.prototype.hideUploadUi=function(){this.$el.find('[data-control="upload-ui"]').addClass('hide')}
MediaManager.prototype.uploadUpdateTotalProgress=function(uploadProgress,totalBytes,totalBytesSent){this.setUploadProgress(uploadProgress)
var fileNumberLabel=this.$el.get(0).querySelector('[data-label="file-number-and-progress"]'),messageTemplate=fileNumberLabel.getAttribute('data-message-template'),fileNumber=this.dropzone.getUploadingFiles().length+this.dropzone.getQueuedFiles().length
if(uploadProgress>=100){uploadProgress=99}fileNumberLabel.innerHTML=messageTemplate.replace(':number',fileNumber).replace(':percents',Math.round(uploadProgress)+'%')}
MediaManager.prototype.setUploadProgress=function(value){var progressBar=this.$el.get(0).querySelector('[data-control="upload-progress-bar"]')
progressBar.setAttribute('style','width: '+value+'%')
progressBar.setAttribute('class','progress-bar')}
MediaManager.prototype.uploadQueueComplete=function(){this.$el.find('[data-command="cancel-uploading"]').addClass('hide')
this.$el.find('[data-command="close-uploader"]').removeClass('hide')
this.refresh()}
MediaManager.prototype.uploadSending=function(file,xhr,formData){formData.append('path',this.$el.find('[data-type="current-folder"]').val())
xhr.setRequestHeader('X-WINTER-REQUEST-HANDLER',this.options.uploadHandler)}
MediaManager.prototype.uploadCancelAll=function(){this.dropzone.removeAllFiles(true)
this.hideUploadUi()}
MediaManager.prototype.updateUploadBar=function(templateName,classNames){var fileNumberLabel=this.$el.get(0).querySelector('[data-label="file-number-and-progress"]'),successTemplate=fileNumberLabel.getAttribute('data-'+templateName+'-template'),progressBar=this.$el.get(0).querySelector('[data-control="upload-progress-bar"]')
fileNumberLabel.innerHTML=successTemplate;progressBar.setAttribute('class',classNames)}
MediaManager.prototype.uploadSuccess=function(){this.updateUploadBar('success','progress-bar progress-bar-success');}
MediaManager.prototype.uploadError=function(file,message){this.updateUploadBar('error','progress-bar progress-bar-danger');if(file.xhr.status===413){message='Server rejected the file because it was too large, try increasing post_max_size';}if(!message){message='Error uploading file'}$.wn.alert(message)}
MediaManager.prototype.cropSelectedImage=function(callback){var selectedItems=this.getSelectedItems(true)
if(selectedItems.length!=1){alert(this.options.selectSingleImage)
return}if(selectedItems[0].getAttribute('data-document-type')!=='image'){alert(this.options.selectionNotImage)
return}var path=selectedItems[0].getAttribute('data-path')
new $.wn.mediaManager.imageCropPopup(path,{alias:this.options.alias,onDone:callback})}
MediaManager.prototype.onImageCropped=function(result){this.$el.trigger('popupcommand',['insert-cropped',result])}
MediaManager.prototype.clearSearchTrackInputTimer=function(){if(this.searchTrackInputTimer===null)return
clearTimeout(this.searchTrackInputTimer)
this.searchTrackInputTimer=null}
MediaManager.prototype.updateSearchResults=function(){var $searchField=this.$el.find('[data-control="search"]'),data={search:$searchField.val()}
this.execNavigationRequest('onSearch',data,$searchField)}
MediaManager.prototype.resetSearch=function(){this.$el.find('[data-control="search"]').val('')}
MediaManager.prototype.onSearchChanged=function(ev){var value=ev.currentTarget.value
if(this.lastSearchValue!==undefined&&this.lastSearchValue==value)return
this.lastSearchValue=value
this.clearSearchTrackInputTimer()
this.searchTrackInputTimer=window.setTimeout(this.proxy(this.updateSearchResults),300)}
MediaManager.prototype.deleteItems=function(){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected')
if(!items.length){$.wn.alert(this.options.deleteEmpty)
return}$.wn.confirm(this.options.deleteConfirm,this.proxy(this.deleteConfirmation))}
MediaManager.prototype.deleteConfirmation=function(confirmed){if(!confirmed)return
var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'),paths=[]
for(var i=0,len=items.length;i<len;i++){if(items[i].hasAttribute('data-root')){continue;}paths.push({'path':items[i].getAttribute('data-path'),'type':items[i].getAttribute('data-item-type')})}var data={paths:paths}
$.wn.stripeLoadIndicator.show()
this.$form.request(this.options.alias+'::onDeleteItem',{data:data}).always(function(){$.wn.stripeLoadIndicator.hide()}).done(this.proxy(this.afterNavigate))}
MediaManager.prototype.createFolder=function(ev){$(ev.target).popup({content:this.$el.find('[data-control="new-folder-template"]').html(),zIndex:1200})}
MediaManager.prototype.onFolderPopupShown=function(ev,button,popup){$(popup).find('input[name=name]').focus()
$(popup).on('submit.media','form',this.proxy(this.onNewFolderSubmit))}
MediaManager.prototype.onFolderPopupHidden=function(ev,button,popup){$(popup).off('.media','form')}
MediaManager.prototype.onNewFolderSubmit=function(ev){var data={name:$(ev.target).find('input[name=name]').val(),path:this.$el.find('[data-type="current-folder"]').val()}
$.wn.stripeLoadIndicator.show()
this.$form.request(this.options.alias+'::onCreateFolder',{data:data}).always(function(){$.wn.stripeLoadIndicator.hide()}).done(this.proxy(this.folderCreated))
ev.preventDefault()
return false}
MediaManager.prototype.folderCreated=function(){this.$el.find('button[data-command="create-folder"]').popup('hide')
this.afterNavigate()}
MediaManager.prototype.moveItems=function(ev){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected')
if(!items.length){$.wn.alert(this.options.moveEmpty)
return}var data={exclude:[],path:this.$el.find('[data-type="current-folder"]').val()}
for(var i=0,len=items.length;i<len;i++){var item=items[i],path=item.getAttribute('data-path')
if(item.getAttribute('data-item-type')=='folder')data.exclude.push(path)}$(ev.target).popup({handler:this.options.alias+'::onLoadMovePopup',extraData:data,zIndex:1200})}
MediaManager.prototype.onMovePopupShown=function(ev,button,popup){$(popup).on('submit.media','form',this.proxy(this.onMoveItemsSubmit))}
MediaManager.prototype.onMoveItemsSubmit=function(ev){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'),data={dest:$(ev.target).find('select[name=dest]').val(),originalPath:$(ev.target).find('input[name=originalPath]').val(),files:[],folders:[]}
for(var i=0,len=items.length;i<len;i++){var item=items[i],path=item.getAttribute('data-path')
if(item.getAttribute('data-item-type')=='folder')data.folders.push(path)
else data.files.push(path)}$.wn.stripeLoadIndicator.show()
this.$form.request(this.options.alias+'::onMoveItems',{data:data}).always(function(){$.wn.stripeLoadIndicator.hide()}).done(this.proxy(this.itemsMoved))
ev.preventDefault()
return false}
MediaManager.prototype.onMovePopupHidden=function(ev,button,popup){$(popup).off('.media','form')}
MediaManager.prototype.itemsMoved=function(){this.$el.find('button[data-command="move"]').popup('hide')
this.afterNavigate()}
MediaManager.prototype.onNavigate=function(ev){var $item=$(ev.target).closest('[data-type="media-item"]')
this.navigateToItem($item)
if($(ev.target).data('label')!='public-url')return false}
MediaManager.prototype.onCommandClick=function(ev){var command=$(ev.currentTarget).data('command')
switch(command){case'refresh':this.refresh()
break;case'change-view':this.changeView($(ev.currentTarget).data('view'))
break;case'cancel-uploading':this.uploadCancelAll()
break;case'close-uploader':this.hideUploadUi()
break;case'set-filter':this.setFilter($(ev.currentTarget).data('filter'))
break;case'delete':this.deleteItems()
break;case'create-folder':this.createFolder(ev)
break;case'move':this.moveItems(ev)
break;case'toggle-sidebar':this.toggleSidebar(ev)
break;case'popup-command':var popupCommand=$(ev.currentTarget).data('popup-command')
if(popupCommand!=='crop-and-insert')this.$el.trigger('popupcommand',[popupCommand])
else this.cropSelectedImage(this.proxy(this.onImageCropped))
break;}return false}
MediaManager.prototype.onItemClick=function(ev){if(ev.target.tagName=='I'&&ev.target.hasAttribute('data-rename-control'))return
this.selectItem(ev.currentTarget,ev.shiftKey)}
MediaManager.prototype.onItemTouch=function(ev){ev.preventDefault()
ev.stopPropagation()
if(this.dblTouchFlag){this.onNavigate(ev)
this.dblTouchFlag=null}else{this.onItemClick(ev)
this.dblTouchFlag=true}this.clearDblTouchTimer()
this.dblTouchTimer=setTimeout(this.proxy(this.clearDblTouchFlag),300)}
MediaManager.prototype.onListMouseDown=function(ev){this.itemListElement.addEventListener('mousemove',this.proxy(this.onListMouseMove))
document.addEventListener('mouseup',this.proxy(this.onListMouseUp))
this.itemListPosition=$.wn.foundation.element.absolutePosition(this.itemListElement,true)
var pagePosition=$.wn.foundation.event.pageCoordinates(ev),relativePosition=this.getRelativePosition(this.itemListElement,pagePosition.x,pagePosition.y,this.itemListPosition)
this.selectionStartPoint=relativePosition
this.selectionStarted=false}
MediaManager.prototype.onListMouseUp=function(ev){this.itemListElement.removeEventListener('mousemove',this.proxy(this.onListMouseMove))
document.removeEventListener('mouseup',this.proxy(this.onListMouseUp))
$(document.body).removeClass('no-select')
if(this.selectionStarted){this.unselectRoot()
var items=this.itemListElement.querySelectorAll('[data-type="media-item"]:not([data-root])'),selectionPosition=$.wn.foundation.element.absolutePosition(this.selectionMarker,true)
for(var index=0,len=items.length;index<len;index++){var item=items[index],itemPosition=$.wn.foundation.element.absolutePosition(item,true)
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=$.wn.foundation.event.pageCoordinates(ev),relativePosition=this.getRelativePosition(this.itemListElement,pagePosition.x,pagePosition.y,this.itemListPosition)
var deltaX=relativePosition.x-this.selectionStartPoint.x,deltaY=relativePosition.y-this.selectionStartPoint.y
if(!this.selectionStarted&&(Math.abs(deltaX)>2||Math.abs(deltaY)>2)){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'}}}
MediaManager.prototype.onSortingChanged=function(ev){var $target=$(ev.target),data={path:this.$el.find('[data-type="current-folder"]').val()}
if($target.data('sort')=='by'){data.sortBy=$target.val();}else if($target.data('sort')=='direction'){data.sortDirection=$target.val()}this.execNavigationRequest('onSetSorting',data)}
MediaManager.prototype.onKeyDown=function(ev){var eventHandled=false
switch(ev.key){case'Enter':var items=this.getSelectedItems(true,true)
if(items.length>0)this.navigateToItem($(items[0]))
eventHandled=true
break;case'ArrowRight':case'ArrowDown':this.selectRelative(true,ev.shiftKey)
eventHandled=true
break;case'ArrowLeft':case'ArrowUp':this.selectRelative(false,ev.shiftKey)
eventHandled=true
break;}if(eventHandled){ev.preventDefault()
ev.stopPropagation()}}
MediaManager.DEFAULTS={url:window.location,uploadHandler:null,alias:'',deleteEmpty:'Please select files to delete.',deleteConfirm:'Delete the selected file(s)?',moveEmpty:'Please select files to move.',selectSingleImage:'Please select a single image.',selectionNotImage:'The selected item is not an image.',bottomToolbar:false,cropAndInsertButton:false}
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
$.fn.mediaManager.noConflict=function(){$.fn.mediaManager=old
return this}
$(document).on('render',function(){$('div[data-control=media-manager]').mediaManager()})}(window.jQuery);+function($){"use strict";if($.wn.mediaManager===undefined)$.wn.mediaManager={}
var Base=$.wn.foundation.base,BaseProto=Base.prototype
var MediaManagerImageCropPopup=function(path,options){this.$popupRootElement=null
this.$popupElement=null
this.selectionSizeLabel=null
this.imageArea=null
this.hRulerHolder=null
this.vRulerHolder=null
this.rulersVisible=false
this.prevScrollTop=0
this.prevScrollLeft=0
this.jCrop=null
this.options=$.extend({},MediaManagerImageCropPopup.DEFAULTS,options)
this.path=path
Base.call(this)
this.init()
this.show()}
MediaManagerImageCropPopup.prototype=Object.create(BaseProto)
MediaManagerImageCropPopup.prototype.constructor=MediaManagerImageCropPopup
MediaManagerImageCropPopup.prototype.dispose=function(){this.unregisterHandlers()
this.removeAttachedControls()
this.$popupRootElement.remove()
this.$popupRootElement=null
this.$popupElement=null
this.selectionSizeLabel=null
this.imageArea=null
this.hRulerHolder=null
this.vRulerHolder=null
BaseProto.dispose.call(this)}
MediaManagerImageCropPopup.prototype.init=function(){if(this.options.alias===undefined)throw new Error('Media Manager image crop popup option "alias" is not set.')
this.$popupRootElement=$('<div/>')
this.registerHandlers()}
MediaManagerImageCropPopup.prototype.show=function(){var data={path:this.path}
this.$popupRootElement.popup({extraData:data,size:'adaptive',adaptiveHeight:true,handler:this.options.alias+'::onLoadImageCropPopup',zIndex:1200})}
MediaManagerImageCropPopup.prototype.registerHandlers=function(){this.$popupRootElement.one('hide.oc.popup',this.proxy(this.onPopupHidden))
this.$popupRootElement.one('shown.oc.popup',this.proxy(this.onPopupShown))}
MediaManagerImageCropPopup.prototype.unregisterHandlers=function(){this.$popupElement.off('change','[data-control="selection-mode"]',this.proxy(this.onSelectionModeChanged))
this.$popupElement.off('click','[data-command]',this.proxy(this.onCommandClick))
this.$popupElement.off('shown.oc.popup','button[data-command=resize]',this.proxy(this.onResizePopupShown))
this.$popupElement.off('hidden.oc.popup','button[data-command=resize]',this.proxy(this.onResizePopupHidden))
if(this.rulersVisible){var $cropToolRoot=this.$popupElement.find('[data-control=media-manager-crop-tool]')
this.imageArea.removeEventListener('scroll',this.proxy(this.onImageScroll))}this.getWidthInput().off('change',this.proxy(this.onSizeInputChange))
this.getHeightInput().off('change',this.proxy(this.onSizeInputChange))}
MediaManagerImageCropPopup.prototype.removeAttachedControls=function(){if(this.$popupElement){this.$popupElement.find('[data-control="selection-mode"]').select2('destroy').remove()
this.$popupElement.find('[data-control=toolbar]').toolbar('dispose').remove()
this.jCrop.destroy()}this.jCrop=null}
MediaManagerImageCropPopup.prototype.hide=function(){if(this.$popupElement)this.$popupElement.trigger('close.oc.popup')}
MediaManagerImageCropPopup.prototype.getSelectionMode=function(){return this.$popupElement.find('[data-control="selection-mode"]').val()}
MediaManagerImageCropPopup.prototype.initRulers=function(){if(!Modernizr.csstransforms)return
var $cropToolRoot=this.$popupElement.find('[data-control=media-manager-crop-tool]'),width=$cropToolRoot.data('image-width'),height=$cropToolRoot.data('image-height')
if(!width||!height)return
if($cropToolRoot.width()>width)width=$(window).width()
if($cropToolRoot.height()>height)height=$(window).height()
$cropToolRoot.find('.ruler-container').removeClass('hide')
$cropToolRoot.addClass('has-rulers')
var $hRuler=$cropToolRoot.find('[data-control=h-ruler]'),$vRuler=$cropToolRoot.find('[data-control=v-ruler]'),hTicks=width/40+1,vTicks=height/40+1
this.createRulerTicks($hRuler,hTicks)
this.createRulerTicks($vRuler,vTicks)
this.rulersVisible=true
this.imageArea.addEventListener('scroll',this.proxy(this.onImageScroll))
this.hRulerHolder=$cropToolRoot.find('.ruler-container.horizontal .layout-relative').get(0)
this.vRulerHolder=$cropToolRoot.find('.ruler-container.vertical .layout-relative').get(0)}
MediaManagerImageCropPopup.prototype.createRulerTicks=function($rulerElement,count){var list=document.createElement('ul')
for(var i=0;i<=count;i++){var li=document.createElement('li')
li.textContent=i*40
list.appendChild(li)}$rulerElement.append(list)}
MediaManagerImageCropPopup.prototype.initJCrop=function(){this.jCrop=$.Jcrop($(this.imageArea).find('img').get(0),{shade:true,onChange:this.proxy(this.onSelectionChanged)})}
MediaManagerImageCropPopup.prototype.fixDimensionValue=function(value){var result=value.replace(/[^0-9]+/,'')
if(!result.length)result=200
if(result=='0')result=1
return result}
MediaManagerImageCropPopup.prototype.getWidthInput=function(){return this.$popupElement.find('[data-control="crop-width-input"]')}
MediaManagerImageCropPopup.prototype.getHeightInput=function(){return this.$popupElement.find('[data-control="crop-height-input"]')}
MediaManagerImageCropPopup.prototype.applySelectionMode=function(){if(!this.jCrop)return
var $widthInput=this.getWidthInput(),$heightInput=this.getHeightInput(),width=this.fixDimensionValue($widthInput.val()),height=this.fixDimensionValue($heightInput.val()),mode=this.getSelectionMode()
switch(mode){case'fixed-ratio':this.jCrop.setOptions({aspectRatio:width/height,minSize:[0,0],maxSize:[0,0],allowResize:true})
break
case'fixed-size':this.jCrop.setOptions({aspectRatio:0,minSize:[width,height],maxSize:[width,height],allowResize:false})
break
case'normal':this.jCrop.setOptions({aspectRatio:0,minSize:[0,0],maxSize:[0,0],allowResize:true})
break}}
MediaManagerImageCropPopup.prototype.cropAndInsert=function(){var data={img:$(this.imageArea).find('img').attr('src'),selection:this.jCrop.tellSelect()}
$.wn.stripeLoadIndicator.show()
this.$popupElement.find('form').request(this.options.alias+'::onCropImage',{data:data}).always(function(){$.wn.stripeLoadIndicator.hide()}).done(this.proxy(this.onImageCropped))}
MediaManagerImageCropPopup.prototype.onImageCropped=function(response){this.hide()
if(this.options.onDone!==undefined){this.options.onDone(response)}}
MediaManagerImageCropPopup.prototype.showResizePopup=function(){this.$popupElement.find('button[data-command=resize]').popup({content:this.$popupElement.find('[data-control="resize-template"]').html(),zIndex:1220})}
MediaManagerImageCropPopup.prototype.onResizePopupShown=function(ev,button,popup){var $popup=$(popup),$widthControl=$popup.find('input[name=width]'),$heightControl=$popup.find('input[name=height]'),imageWidth=this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-width]').val()),imageHeight=this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-height]').val())
$widthControl.val(imageWidth)
$heightControl.val(imageHeight)
$widthControl.focus()
$popup.on('submit.media','form',this.proxy(this.onResizeSubmit))
$widthControl.on('keyup.media',this.proxy(this.onResizeDimensionKeyUp))
$heightControl.on('keyup.media',this.proxy(this.onResizeDimensionKeyUp))
$widthControl.on('change.media',this.proxy(this.onResizeDimensionChanged))
$heightControl.on('change.media',this.proxy(this.onResizeDimensionChanged))}
MediaManagerImageCropPopup.prototype.onResizePopupHidden=function(ev,button,popup){var $popup=$(popup),$widthControl=$popup.find('input[name=width]'),$heightControl=$popup.find('input[name=height]')
$popup.off('.media','form')
$widthControl.off('.media')
$heightControl.off('.media')}
MediaManagerImageCropPopup.prototype.onResizeDimensionKeyUp=function(ev){var $target=$(ev.target),targetValue=this.fixDimensionValue($target.val()),otherDimensionName=$target.attr('name')=='width'?'height':'width',$otherInput=$target.closest('form').find('input[name='+otherDimensionName+']'),ratio=this.$popupElement.find('[data-control=original-ratio]').val(),value=otherDimensionName=='height'?targetValue/ratio:targetValue*ratio
$otherInput.val(Math.round(value))}
MediaManagerImageCropPopup.prototype.onResizeDimensionChanged=function(ev){var $target=$(ev.target)
$target.val(this.fixDimensionValue($target.val()))}
MediaManagerImageCropPopup.prototype.onResizeSubmit=function(ev){var data={cropSessionKey:this.$popupElement.find('input[name=cropSessionKey]').val(),path:this.$popupElement.find('input[name=path]').val()}
$.wn.stripeLoadIndicator.show()
$(ev.target).request(this.options.alias+'::onResizeImage',{data:data}).always(function(){$.wn.stripeLoadIndicator.hide()}).done(this.proxy(this.imageResized))
ev.preventDefault()
return false}
MediaManagerImageCropPopup.prototype.imageResized=function(response){this.$popupElement.find('button[data-command=resize]').popup('hide')
this.updateImage(response.url,response.dimensions[0],response.dimensions[1])}
MediaManagerImageCropPopup.prototype.updateImage=function(url,width,hegiht){this.jCrop.destroy()
this.$popupElement.find('span[data-label=width]').text(width)
this.$popupElement.find('span[data-label=height]').text(hegiht)
this.$popupElement.find('input[data-control=dimension-width]').val(width)
this.$popupElement.find('input[data-control=dimension-height]').val(hegiht)
var $imageArea=$(this.imageArea)
$imageArea.find('img').remove()
var $img=$('<img>').attr('src',url)
$img.one('load',this.proxy(this.initJCrop))
$imageArea.append($img)
this.imageArea.scrollTop=0
this.imageArea.scrollLeft=0
this.onImageScroll()}
MediaManagerImageCropPopup.prototype.undoResizing=function(){this.updateImage(this.$popupElement.find('input[data-control=original-url]').val(),this.$popupElement.find('input[data-control=original-width]').val(),this.$popupElement.find('input[data-control=original-height]').val())}
MediaManagerImageCropPopup.prototype.updateSelectionSizeLabel=function(width,height){if(width==0&&height==0){this.selectionSizeLabel.setAttribute('class','hide')
return}this.selectionSizeLabel.setAttribute('class','')
this.selectionSizeLabel.querySelector('[data-label=selection-width]').textContent=parseInt(width)
this.selectionSizeLabel.querySelector('[data-label=selection-height]').textContent=parseInt(height)}
MediaManagerImageCropPopup.prototype.onPopupHidden=function(event,element,popup){$(document).trigger('mousedown')
this.dispose()}
MediaManagerImageCropPopup.prototype.onPopupShown=function(event,element,popup){this.$popupElement=popup
this.$popupElement.on('change','[data-control="selection-mode"]',this.proxy(this.onSelectionModeChanged))
this.$popupElement.on('click','[data-command]',this.proxy(this.onCommandClick))
this.$popupElement.on('shown.oc.popup','button[data-command=resize]',this.proxy(this.onResizePopupShown))
this.$popupElement.on('hidden.oc.popup','button[data-command=resize]',this.proxy(this.onResizePopupHidden))
this.imageArea=popup.find('[data-control=media-manager-crop-tool]').get(0).querySelector('.image_area')
this.selectionSizeLabel=popup.find('[data-label="selection-size"]').get(0)
this.getWidthInput().on('change',this.proxy(this.onSizeInputChange))
this.getHeightInput().on('change',this.proxy(this.onSizeInputChange))
this.initRulers()
this.initJCrop()
this.applySelectionMode()}
MediaManagerImageCropPopup.prototype.onSelectionModeChanged=function(){var mode=this.getSelectionMode(),$widthInput=this.getWidthInput(),$heightInput=this.getHeightInput()
if(mode==='normal'){$widthInput.attr('disabled','disabled')
$heightInput.attr('disabled','disabled')}else{$widthInput.removeAttr('disabled')
$heightInput.removeAttr('disabled')
$widthInput.val(this.fixDimensionValue($widthInput.val()))
$heightInput.val(this.fixDimensionValue($heightInput.val()))}this.applySelectionMode()}
MediaManagerImageCropPopup.prototype.onImageScroll=function(){var scrollTop=this.imageArea.scrollTop,scrollLeft=this.imageArea.scrollLeft
if(this.prevScrollTop!=scrollTop){this.prevScrollTop=scrollTop
this.vRulerHolder.scrollTop=scrollTop}if(this.prevScrollLeft!=scrollLeft){this.prevScrollLeft=scrollLeft
this.hRulerHolder.scrollLeft=scrollLeft}}
MediaManagerImageCropPopup.prototype.onSizeInputChange=function(ev){var $target=$(ev.target)
$target.val(this.fixDimensionValue($target.val()))
this.applySelectionMode()}
MediaManagerImageCropPopup.prototype.onCommandClick=function(ev){var command=$(ev.currentTarget).data('command')
switch(command){case'insert':this.cropAndInsert()
break
case'resize':this.showResizePopup()
break
case'undo-resizing':this.undoResizing()
break}}
MediaManagerImageCropPopup.prototype.onSelectionChanged=function(c){this.updateSelectionSizeLabel(c.w,c.h)}
MediaManagerImageCropPopup.DEFAULTS={alias:undefined,onDone:undefined}
$.wn.mediaManager.imageCropPopup=MediaManagerImageCropPopup}(window.jQuery);

View File

@@ -0,0 +1,12 @@
/*
* This is a bundle file, you can compile this by running
*
* php artisan winter:util compile assets
*
* @see build-min.js
*
=require mediamanager.js
=require mediamanager.imagecroppopup.js
*/

View File

@@ -0,0 +1,6 @@
/*
These scripts will be included globally as part of the backend.
=require mediamanager.popup.js
*/

View File

@@ -0,0 +1,478 @@
/*
* Media manager image editor popup
*/
+function ($) { "use strict";
if ($.wn.mediaManager === undefined)
$.wn.mediaManager = {}
var Base = $.wn.foundation.base,
BaseProto = Base.prototype
var MediaManagerImageCropPopup = function(path, options) {
this.$popupRootElement = null
this.$popupElement = null
this.selectionSizeLabel = null
this.imageArea = null
this.hRulerHolder = null
this.vRulerHolder = null
this.rulersVisible = false
this.prevScrollTop = 0
this.prevScrollLeft = 0
this.jCrop = null
this.options = $.extend({}, MediaManagerImageCropPopup.DEFAULTS, options)
this.path = path
Base.call(this)
this.init()
this.show()
}
MediaManagerImageCropPopup.prototype = Object.create(BaseProto)
MediaManagerImageCropPopup.prototype.constructor = MediaManagerImageCropPopup
MediaManagerImageCropPopup.prototype.dispose = function() {
this.unregisterHandlers()
this.removeAttachedControls()
this.$popupRootElement.remove()
this.$popupRootElement = null
this.$popupElement = null
this.selectionSizeLabel = null
this.imageArea = null
this.hRulerHolder = null
this.vRulerHolder = null
BaseProto.dispose.call(this)
}
MediaManagerImageCropPopup.prototype.init = function() {
if (this.options.alias === undefined)
throw new Error('Media Manager image crop popup option "alias" is not set.')
this.$popupRootElement = $('<div/>')
this.registerHandlers()
}
MediaManagerImageCropPopup.prototype.show = function() {
var data = {
path: this.path
}
this.$popupRootElement.popup({
extraData: data,
size: 'adaptive',
adaptiveHeight: true,
handler: this.options.alias + '::onLoadImageCropPopup',
zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index
})
}
MediaManagerImageCropPopup.prototype.registerHandlers = function() {
this.$popupRootElement.one('hide.oc.popup', this.proxy(this.onPopupHidden))
this.$popupRootElement.one('shown.oc.popup', this.proxy(this.onPopupShown))
}
MediaManagerImageCropPopup.prototype.unregisterHandlers = function() {
this.$popupElement.off('change', '[data-control="selection-mode"]', this.proxy(this.onSelectionModeChanged))
this.$popupElement.off('click', '[data-command]', this.proxy(this.onCommandClick))
this.$popupElement.off('shown.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupShown))
this.$popupElement.off('hidden.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupHidden))
if (this.rulersVisible) {
var $cropToolRoot = this.$popupElement.find('[data-control=media-manager-crop-tool]')
this.imageArea.removeEventListener('scroll', this.proxy(this.onImageScroll))
}
this.getWidthInput().off('change', this.proxy(this.onSizeInputChange))
this.getHeightInput().off('change', this.proxy(this.onSizeInputChange))
}
MediaManagerImageCropPopup.prototype.removeAttachedControls = function() {
if (this.$popupElement) {
// Note - the controls are destroyed and removed from DOM. If they're just destroyed,
// the JS plugins could be re-attached to them on window.onresize. -ab
this.$popupElement.find('[data-control="selection-mode"]').select2('destroy').remove()
this.$popupElement.find('[data-control=toolbar]').toolbar('dispose').remove()
this.jCrop.destroy()
}
this.jCrop = null
}
MediaManagerImageCropPopup.prototype.hide = function() {
if (this.$popupElement)
this.$popupElement.trigger('close.oc.popup')
}
MediaManagerImageCropPopup.prototype.getSelectionMode = function() {
return this.$popupElement.find('[data-control="selection-mode"]').val()
}
MediaManagerImageCropPopup.prototype.initRulers = function() {
if (!Modernizr.csstransforms)
return
var $cropToolRoot = this.$popupElement.find('[data-control=media-manager-crop-tool]'),
width = $cropToolRoot.data('image-width'),
height = $cropToolRoot.data('image-height')
if (!width || !height)
return
if ($cropToolRoot.width() > width)
width = $(window).width()
if ($cropToolRoot.height() > height)
height = $(window).height()
$cropToolRoot.find('.ruler-container').removeClass('hide')
$cropToolRoot.addClass('has-rulers')
var $hRuler = $cropToolRoot.find('[data-control=h-ruler]'),
$vRuler = $cropToolRoot.find('[data-control=v-ruler]'),
hTicks = width / 40 + 1,
vTicks = height / 40 + 1
this.createRulerTicks($hRuler, hTicks)
this.createRulerTicks($vRuler, vTicks)
this.rulersVisible = true
this.imageArea.addEventListener('scroll', this.proxy(this.onImageScroll))
this.hRulerHolder = $cropToolRoot.find('.ruler-container.horizontal .layout-relative').get(0)
this.vRulerHolder = $cropToolRoot.find('.ruler-container.vertical .layout-relative').get(0)
}
MediaManagerImageCropPopup.prototype.createRulerTicks = function($rulerElement, count) {
var list = document.createElement('ul')
for (var i=0; i <= count; i++) {
var li = document.createElement('li')
li.textContent = i*40
list.appendChild(li)
}
$rulerElement.append(list)
}
MediaManagerImageCropPopup.prototype.initJCrop = function() {
this.jCrop = $.Jcrop($(this.imageArea).find('img').get(0), {
shade: true,
onChange: this.proxy(this.onSelectionChanged)
})
}
MediaManagerImageCropPopup.prototype.fixDimensionValue = function(value) {
var result = value.replace(/[^0-9]+/, '')
if (!result.length)
result = 200
if (result == '0')
result = 1
return result
}
MediaManagerImageCropPopup.prototype.getWidthInput = function() {
return this.$popupElement.find('[data-control="crop-width-input"]')
}
MediaManagerImageCropPopup.prototype.getHeightInput = function() {
return this.$popupElement.find('[data-control="crop-height-input"]')
}
MediaManagerImageCropPopup.prototype.applySelectionMode = function() {
if (!this.jCrop)
return
var $widthInput = this.getWidthInput(),
$heightInput = this.getHeightInput(),
width = this.fixDimensionValue($widthInput.val()),
height = this.fixDimensionValue($heightInput.val()),
mode = this.getSelectionMode()
switch (mode) {
case 'fixed-ratio' :
this.jCrop.setOptions({
aspectRatio: width/height,
minSize: [0, 0],
maxSize: [0, 0],
allowResize: true
})
break
case 'fixed-size' :
this.jCrop.setOptions({
aspectRatio: 0,
minSize: [width, height],
maxSize: [width, height],
allowResize: false
})
break
case 'normal' :
this.jCrop.setOptions({
aspectRatio: 0,
minSize: [0, 0],
maxSize: [0, 0],
allowResize: true
})
break
}
}
MediaManagerImageCropPopup.prototype.cropAndInsert = function() {
var data = {
img: $(this.imageArea).find('img').attr('src'),
selection: this.jCrop.tellSelect()
}
$.wn.stripeLoadIndicator.show()
this.$popupElement
.find('form')
.request(this.options.alias+'::onCropImage', {
data: data
})
.always(function() {
$.wn.stripeLoadIndicator.hide()
})
.done(this.proxy(this.onImageCropped))
}
MediaManagerImageCropPopup.prototype.onImageCropped = function(response) {
this.hide()
if (this.options.onDone !== undefined) {
this.options.onDone(response)
}
}
MediaManagerImageCropPopup.prototype.showResizePopup = function() {
this.$popupElement.find('button[data-command=resize]').popup({
content: this.$popupElement.find('[data-control="resize-template"]').html(),
zIndex: 1220
})
}
MediaManagerImageCropPopup.prototype.onResizePopupShown = function(ev, button, popup) {
var $popup = $(popup),
$widthControl = $popup.find('input[name=width]'),
$heightControl = $popup.find('input[name=height]'),
imageWidth = this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-width]').val()),
imageHeight = this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-height]').val())
$widthControl.val(imageWidth)
$heightControl.val(imageHeight)
$widthControl.focus()
$popup.on('submit.media', 'form', this.proxy(this.onResizeSubmit))
$widthControl.on('keyup.media', this.proxy(this.onResizeDimensionKeyUp))
$heightControl.on('keyup.media', this.proxy(this.onResizeDimensionKeyUp))
$widthControl.on('change.media', this.proxy(this.onResizeDimensionChanged))
$heightControl.on('change.media', this.proxy(this.onResizeDimensionChanged))
}
MediaManagerImageCropPopup.prototype.onResizePopupHidden = function(ev, button, popup) {
var $popup = $(popup),
$widthControl = $popup.find('input[name=width]'),
$heightControl = $popup.find('input[name=height]')
$popup.off('.media', 'form')
$widthControl.off('.media')
$heightControl.off('.media')
}
MediaManagerImageCropPopup.prototype.onResizeDimensionKeyUp = function(ev) {
var $target = $(ev.target),
targetValue = this.fixDimensionValue($target.val()),
otherDimensionName = $target.attr('name') == 'width' ? 'height' : 'width',
$otherInput = $target.closest('form').find('input[name='+otherDimensionName+']'),
ratio = this.$popupElement.find('[data-control=original-ratio]').val(),
value = otherDimensionName == 'height' ? targetValue / ratio : targetValue * ratio
$otherInput.val(Math.round(value))
}
MediaManagerImageCropPopup.prototype.onResizeDimensionChanged = function(ev) {
var $target = $(ev.target)
$target.val(this.fixDimensionValue($target.val()))
}
MediaManagerImageCropPopup.prototype.onResizeSubmit = function(ev) {
var data = {
cropSessionKey: this.$popupElement.find('input[name=cropSessionKey]').val(),
path: this.$popupElement.find('input[name=path]').val()
}
$.wn.stripeLoadIndicator.show()
$(ev.target).request(this.options.alias+'::onResizeImage', {
data: data
}).always(function() {
$.wn.stripeLoadIndicator.hide()
}).done(this.proxy(this.imageResized))
ev.preventDefault()
return false
}
MediaManagerImageCropPopup.prototype.imageResized = function(response) {
this.$popupElement.find('button[data-command=resize]').popup('hide')
this.updateImage(response.url, response.dimensions[0], response.dimensions[1])
}
MediaManagerImageCropPopup.prototype.updateImage = function(url, width, hegiht) {
this.jCrop.destroy()
this.$popupElement.find('span[data-label=width]').text(width)
this.$popupElement.find('span[data-label=height]').text(hegiht)
this.$popupElement.find('input[data-control=dimension-width]').val(width)
this.$popupElement.find('input[data-control=dimension-height]').val(hegiht)
var $imageArea = $(this.imageArea)
$imageArea.find('img').remove()
var $img = $('<img>').attr('src', url)
$img.one('load', this.proxy(this.initJCrop))
$imageArea.append($img)
this.imageArea.scrollTop = 0
this.imageArea.scrollLeft = 0
this.onImageScroll()
}
MediaManagerImageCropPopup.prototype.undoResizing = function() {
this.updateImage(
this.$popupElement.find('input[data-control=original-url]').val(),
this.$popupElement.find('input[data-control=original-width]').val(),
this.$popupElement.find('input[data-control=original-height]').val()
)
}
MediaManagerImageCropPopup.prototype.updateSelectionSizeLabel = function(width, height) {
if (width == 0 && height == 0) {
this.selectionSizeLabel.setAttribute('class', 'hide')
return
}
this.selectionSizeLabel.setAttribute('class', '')
this.selectionSizeLabel.querySelector('[data-label=selection-width]').textContent = parseInt(width)
this.selectionSizeLabel.querySelector('[data-label=selection-height]').textContent = parseInt(height)
}
// EVENT HANDLERS
// ============================
MediaManagerImageCropPopup.prototype.onPopupHidden = function(event, element, popup) {
// Release clickedElement reference inside redactor.js
// If we don't do it, the image editor popup DOM elements
// won't be removed from the memory.
$(document).trigger('mousedown')
this.dispose()
}
MediaManagerImageCropPopup.prototype.onPopupShown = function(event, element, popup) {
this.$popupElement = popup
this.$popupElement.on('change', '[data-control="selection-mode"]', this.proxy(this.onSelectionModeChanged))
this.$popupElement.on('click', '[data-command]', this.proxy(this.onCommandClick))
this.$popupElement.on('shown.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupShown))
this.$popupElement.on('hidden.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupHidden))
this.imageArea = popup.find('[data-control=media-manager-crop-tool]').get(0).querySelector('.image_area')
this.selectionSizeLabel = popup.find('[data-label="selection-size"]').get(0)
this.getWidthInput().on('change', this.proxy(this.onSizeInputChange))
this.getHeightInput().on('change', this.proxy(this.onSizeInputChange))
this.initRulers()
this.initJCrop()
this.applySelectionMode()
}
MediaManagerImageCropPopup.prototype.onSelectionModeChanged = function() {
var mode = this.getSelectionMode(),
$widthInput = this.getWidthInput(),
$heightInput = this.getHeightInput()
if (mode === 'normal') {
$widthInput.attr('disabled', 'disabled')
$heightInput.attr('disabled', 'disabled')
}
else {
$widthInput.removeAttr('disabled')
$heightInput.removeAttr('disabled')
$widthInput.val(this.fixDimensionValue($widthInput.val()))
$heightInput.val(this.fixDimensionValue($heightInput.val()))
}
this.applySelectionMode()
}
MediaManagerImageCropPopup.prototype.onImageScroll = function() {
var scrollTop = this.imageArea.scrollTop,
scrollLeft = this.imageArea.scrollLeft
if (this.prevScrollTop != scrollTop) {
this.prevScrollTop = scrollTop
this.vRulerHolder.scrollTop = scrollTop
}
if (this.prevScrollLeft != scrollLeft) {
this.prevScrollLeft = scrollLeft
this.hRulerHolder.scrollLeft = scrollLeft
}
}
MediaManagerImageCropPopup.prototype.onSizeInputChange = function(ev) {
var $target = $(ev.target)
$target.val(this.fixDimensionValue($target.val()))
this.applySelectionMode()
}
MediaManagerImageCropPopup.prototype.onCommandClick = function(ev) {
var command = $(ev.currentTarget).data('command')
switch (command) {
case 'insert':
this.cropAndInsert()
break
case 'resize':
this.showResizePopup()
break
case 'undo-resizing':
this.undoResizing()
break
}
}
MediaManagerImageCropPopup.prototype.onSelectionChanged = function(c) {
this.updateSelectionSizeLabel(c.w, c.h)
}
MediaManagerImageCropPopup.DEFAULTS = {
alias: undefined,
onDone: undefined
}
$.wn.mediaManager.imageCropPopup = MediaManagerImageCropPopup
}(window.jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
/*
* Media manager popup
*/
+function ($) { "use strict";
if ($.wn.mediaManager === undefined)
$.wn.mediaManager = {}
var Base = $.wn.foundation.base,
BaseProto = Base.prototype
var MediaManagerPopup = function(options) {
this.$popupRootElement = null
this.options = $.extend({}, MediaManagerPopup.DEFAULTS, options)
Base.call(this)
this.init()
this.show()
}
MediaManagerPopup.prototype = Object.create(BaseProto)
MediaManagerPopup.prototype.constructor = MediaManagerPopup
MediaManagerPopup.prototype.dispose = function() {
this.unregisterHandlers()
this.$popupRootElement.remove()
this.$popupRootElement = null
this.$popupElement = null
BaseProto.dispose.call(this)
}
MediaManagerPopup.prototype.init = function() {
if (this.options.alias === undefined)
throw new Error('Media Manager popup option "alias" is not set.')
this.$popupRootElement = $('<div/>')
this.registerHandlers()
}
MediaManagerPopup.prototype.registerHandlers = function() {
this.$popupRootElement.one('hide.oc.popup', this.proxy(this.onPopupHidden))
this.$popupRootElement.one('shown.oc.popup', this.proxy(this.onPopupShown))
}
MediaManagerPopup.prototype.unregisterHandlers = function() {
this.$popupElement.off('popupcommand', this.proxy(this.onPopupCommand))
this.$popupRootElement.off('popupcommand', this.proxy(this.onPopupCommand))
}
MediaManagerPopup.prototype.show = function() {
var data = {
bottomToolbar: this.options.bottomToolbar ? 1 : 0,
cropAndInsertButton: this.options.cropAndInsertButton ? 1 : 0,
mode: this.options.mode || 'all',
}
this.$popupRootElement.popup({
extraData: data,
size: 'adaptive',
adaptiveHeight: true,
handler: this.options.alias + '::onLoadPopup'
})
}
MediaManagerPopup.prototype.hide = function() {
if (this.$popupElement)
this.$popupElement.trigger('close.oc.popup')
}
MediaManagerPopup.prototype.getMediaManagerElement = function() {
return this.$popupElement.find('[data-control="media-manager"]')
}
MediaManagerPopup.prototype.insertMedia = function() {
var items = this.getMediaManagerElement().mediaManager('getSelectedItems')
if (this.options.onInsert !== undefined)
this.options.onInsert.call(this, items)
}
MediaManagerPopup.prototype.insertCroppedImage = function(imageItem) {
if (this.options.onInsert !== undefined)
this.options.onInsert.call(this, [imageItem])
}
// EVENT HANDLERS
// ============================
MediaManagerPopup.prototype.onPopupHidden = function(event, element, popup) {
var mediaManager = this.getMediaManagerElement()
mediaManager.mediaManager('dispose')
mediaManager.remove()
// Release clickedElement reference inside redactor.js
// If we don't do it, the Media Manager popup DOM elements
// won't be removed from the memory.
$(document).trigger('mousedown')
this.dispose()
if (this.options.onClose !== undefined)
this.options.onClose.call(this)
}
MediaManagerPopup.prototype.onPopupShown = function(event, element, popup) {
this.$popupElement = popup
this.$popupElement.on('popupcommand', this.proxy(this.onPopupCommand))
// Unfocus the Redactor field, otherwise all keyboard commands
// in the Media Manager popup translate to Redactor.
this.getMediaManagerElement().mediaManager('selectFirstItem')
}
MediaManagerPopup.prototype.onPopupCommand = function(ev, command, param) {
switch (command) {
case 'insert' :
this.insertMedia()
break;
case 'insert-cropped' :
this.insertCroppedImage(param)
break;
}
return false
}
MediaManagerPopup.DEFAULTS = {
alias: undefined,
bottomToolbar: true,
cropAndInsertButton: false,
onInsert: undefined,
onClose: undefined
}
$.wn.mediaManager.popup = MediaManagerPopup
}(window.jQuery);

View File

@@ -0,0 +1,803 @@
@import "../../../../../backend/assets/less/core/boot.less";
@color-media-list-hover-bg: mix(white, @brand-secondary, 13%);
.media-selected-tiles() {
.icon-container {
background: @color-media-list-hover-bg !important;
border-color: #2581b8;
i, p {
color: #ecf0f1;
}
}
h4 {
color: #2581b8;
}
}
.media-selected-list() {
background: @color-media-list-hover-bg !important;
i, p.size {
color: #ecf0f1;
}
h4 {
color: white;
}
.icon-container {
border-right-color: @color-media-list-hover-bg !important;
}
}
div[data-control="media-manager"] {
&:focus {
outline: none;
}
.loading-indicator-pseudo-absolute(@size) {
background-image: url('../../../../../../modules/system/assets/ui/images/loader-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;
}
.file-icon {
fill-rule: evenodd;
clip-rule: evenodd;
stroke-linejoin: round;
stroke-miterlimit: 2;
display: inline-block;
&-extension {
font-family: 'ArialMT','Arial',sans-serif;
font-size: 4em;
font-weight: 900;
fill: #fff;
}
&-label {
fill: #576D7E; /* Default color */
fill-rule: nonzero;
}
&-css,
&-less,
&-scss {
fill: #B73FD9;
}
&-html,
&-xml {
fill: #EA9B47;
}
&-js,
&-json {
fill: #A9A9A9;
}
&-pdf {
fill: #E30713;
}
&-txt {
fill: #248BD0;
}
&-ai {
fill: #F29200;
}
&-eps {
fill: #F9B234;
}
&-psd {
fill: #2DAAE2;
}
&-ttf,
&-otf,
&-woff,
&-woff2 {
fill: #C4CA10;
}
&-doc,
&-docx,
&-rtf,
&-odt {
fill: #0F70B7;
}
&-csv,
&-ods,
&-xls,
&-xlsx {
fill: #3BAA34;
}
&-odp,
&-ppt,
&-pptx {
fill: #D04526;
}
&-rar,
&-tar,
&-zip {
fill: #363A56;
}
}
.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: 10px;
line-height: 160%;
color: #bdc3c7;
}
.empty-library {
padding: 20px;
text-align: center;
}
p.thumbnail-error-message {
.icon-message();
}
.media-list {
padding: 0 0 0 20px;
margin: 0;
.user-select(none);
li {
display: inline-block;
vertical-align: top;
margin: 0 20px 20px 0;
overflow: hidden;
cursor: pointer;
.border-radius(3px);
&:focus {
outline: none;
}
.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;
line-height: 150%;
margin: 15px 0 5px 0;
padding-right: 0;
.transition(padding 0.1s);
position: relative;
a {
position: absolute;
right: 0;
top: 0;
font-size: 15px;
color: #2b3e50;
display: none;
&:hover {
color: @link-color;
text-decoration: none;
}
}
}
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-media-list-hover-bg;
}
}
&.list {
li {
height: 75px;
width: 260px;
border: 1px solid #ecf0f1;
background: #f6f8f9;
box-sizing: content-box;
}
li .icon-container {
border-right: 1px solid #f6f8f9;
width: 75px;
height: 75px;
float: left;
img {
max-height: 75px;
}
i {
font-size: 35px;
}
svg {
max-height: 44px;
}
&.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();
}
h4 {
padding-right: 15px;
a {
right: 15px;
}
}
}
&.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: 165px;
height: 165px;
.border-radius(3px);
border: 1px solid #ecf0f1;
overflow: hidden;
background: #f6f8f9;
box-sizing: content-box;
img {
max-height: 165px;
}
i {
font-size: 55px;
}
svg {
max-height: 65px;
}
p {
font-family: @font-family-base;
}
}
li.selected {
.media-selected-tiles();
}
i.icon-chain-broken {
margin-top: 47px;
}
p.size {
margin-bottom: 0;
}
}
}
[data-control="sidebar-labels"] {
word-wrap: break-word;
}
.sidebar-group {
margin-bottom: 20px;
}
.sidebar-image-placeholder-container,
.sidebar-document-placeholder-container {
display: table;
width: 100%;
}
.sidebar-image-placeholder,
.sidebar-document-placeholder {
display: table-cell;
position: relative;
vertical-align: middle;
text-align: center;
border-bottom: 1px solid #ecf0f1;
box-sizing: content-box;
}
.sidebar-image-placeholder {
height: 225px;
&[data-loading] {
background: #ecf0f1;
&:after {
.loading-indicator-pseudo-absolute(62px);
}
}
i.icon-chain-broken, i.icon-crop, i.icon-asterisk, i.icon-level-up {
color: #bdc3c7;
font-size: 55px;
}
&.no-border {
border-bottom: none;
}
p {
.icon-message();
margin-top: 25px;
}
img {
max-width: 100%;
max-height: 225px;
}
}
.sidebar-document-placeholder {
height: 155px;
svg {
width: 100px;
height: 100px;
}
}
.list-container {
position: relative;
z-index: 100;
.no-data {
font-size: 13px;
}
p.no-data {
padding: 0 20px 20px 20px;
}
li.no-data {
padding-top: 20px;
display: block !important;
width: 100% !important;
border: none !important;
background: transparent !important;
cursor: default !important;
}
table.table.data {
tbody tr:not(.no-data):active td {
background: @color-list-hover-bg !important;
}
}
}
[data-control="item-list"] {
position: relative;
display: table-cell;
}
.control-scrollpad {
position: absolute;
left: 0;
top: 0;
// Prevents erratic rendering issues when the height is
// sometimes calculated as 0 then repeatedly redrawn
min-height: 300px;
}
.scroll-wrapper {
position: relative;
}
table.table {
table-layout: fixed;
margin-bottom: 0;
white-space: nowrap;
div.no-wrap-text {
overflow: hidden;
text-overflow: ellipsis;
}
div.item-title {
position: relative;
padding-right: 0;
.transition(padding 0.1s);
a {
position: absolute;
right: 0;
top: 0;
display: none;
}
}
tr:hover div.item-title {
padding-right: 25px;
a {
display: block;
}
}
tr[data-item-type=folder] i.icon-folder {
color: @color-media-list-hover-bg;
}
tr:focus {
outline: none;
}
}
div[data-control="selection-marker"] {
position: absolute;
z-index: 250;
border: 1px dashed #95a5a6;
background: rgba(0,0,0,0.1);
}
.upload-progress {
background: @body-bg;
padding: 0 20px;
h5 {
margin: 0 0 10px 0;
font-size: 13px;
color: #2b3e50;
font-weight: 600;
span {
display: inline-block;
margin-left: 10px;
color: #95a5a6;
font-size: 15px;
}
}
.progress-controls {
padding-right: 30px;
position: relative;
.controls {
position: absolute;
right: 0;
bottom: 0;
a {
display: block;
position: relative;
top: 7px;
right: 3px;
color: #95a5a6;
font-size: 16px;
cursor: pointer!important;
&:hover {
text-decoration: none;
color: @link-color;
}
}
}
}
}
.dz-preview {
display: none;
}
button[data-command="toggle-sidebar"] {
&.sidebar-hidden {
.transform( ~'rotate(180deg) translate(0, 0)' );
}
}
}
[data-control="media-manager-crop-tool"] {
.image_area {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
.jcrop-holder {
background-color: transparent!important;
}
}
img {
cursor: crosshair;
display: block;
}
&.has-rulers {
.ruler-container {
.layout-relative {
overflow: hidden;
}
&.horizontal {
.layout-cell {
height: 20px;
}
.layout-relative {
width: 100%;
}
}
&.vertical {
width: 20px;
.layout-relative {
height: 100%;
}
}
}
.ruler {
position: absolute;
height: 20px;
margin-left: -3px;
background: #555;
ul {
margin: 0;
padding: 0;
white-space: nowrap;
font-size: 0;
}
li {
margin: 0;
padding: 0 0 0 40px;
list-style: none;
display: inline-block;
width: 24px;
margin: 0px -10px 0px -14px;
.box-sizing(content-box);
text-align: left;
position: relative;
font-size: 10px;
line-height: 20px;
color: #ecf0f1;
font-family: Arial, sans-serif;
&:before, &:after {
content: ' ';
position: absolute;
border-left: 1px solid #8e8e8e;
}
&:before {
height: 20px;
top: 0;
left: -3px;
}
&:after {
height: 3px;
bottom: 0;
left: 20px;
}
&:first-child:after {
display: none;
}
}
}
.ruler[data-control=v-ruler] {
.transform(~'rotateZ(90deg)');
.transform-origin(~'left top');
left: 23px;
top: -23px;
& li:after {
top: 0;
left: auto;
}
}
}
}
body:not(.no-select) {
div[data-control="media-manager"] .media-list {
&.tiles {
li:hover {
.media-selected-tiles();
}
li:hover h4 {
padding-right: 20px !important;
}
}
&.list {
li:hover {
.media-selected-list();
}
li:hover h4 {
padding-right: 35px !important;
}
}
li {
&:hover h4 a {
display: block;
}
}
}
}
@media (max-width: 1280px) {
div[data-control="media-manager"] {
.media-list {
&.list {
li {
width: 230px;
}
}
}
}
}
@media (max-width: 1024px) {
div[data-control="media-manager"] {
.media-list {
&.list {
li {
display: block;
width: auto;
}
}
}
}
}
@media (max-width: @screen-sm) {
div[data-control="media-manager"] {
[data-control="preview-sidebar"],
[data-command="toggle-sidebar"] {
display: none!important;
}
.media-list {
&.list {
padding: 0;
li {
.border-radius(0);
margin: 0;
border-right: none;
border-left: none;
border-bottom: none;
}
}
}
}
}
@media (max-width: 480px) {
div[data-control="media-manager"] {
[data-control="left-sidebar"] {
display: none!important;
}
}
}

View File

@@ -0,0 +1,70 @@
<div
data-control="media-manager"
class="layout"
data-alias="<?= $this->alias ?>"
data-upload-handler="<?= $this->getEventHandler('onUpload') ?>"
data-delete-empty="<?= e(trans('backend::lang.media.delete_empty')) ?>"
data-delete-confirm="<?= e(trans('backend::lang.media.delete_confirm')) ?>"
data-move-empty="<?= e(trans('backend::lang.media.move_empty')) ?>"
data-select-single-image="<?= e(trans('backend::lang.media.select_single_image')) ?>"
data-selection-not-image="<?= e(trans('backend::lang.media.selection_not_image')) ?>"
data-bottom-toolbar="<?= $this->bottomToolbar ? 'true' : 'false' ?>"
data-crop-and-insert-button="<?= $this->cropAndInsertButton ? 'true' : 'false' ?>"
data-read-only="<?= $this->readOnly ? 'true' : 'false'; ?>"
tabindex="0"
>
<?= $this->makePartial('toolbar') ?>
<?= (!$this->readOnly) ? $this->makePartial('upload-progress') : '' ?>
<div class="layout-row whiteboard">
<div class="layout">
<div class="layout-row">
<div class="layout-cell panel w-200 border-right" data-control="left-sidebar">
<?= $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-row">
<div class="layout">
<!-- Main area - list -->
<div data-control="item-list">
<div class="control-scrollpad">
<div class="scroll-wrapper"> <!-- This element is required for the scrollpad control -->
<div id="<?= $this->getId('item-list') ?>" >
<?= $this->makePartial('item-list') ?>
</div>
</div>
</div>
</div>
<div class="layout-cell w-300 panel border-left no-padding <?= !$sidebarVisible ? 'hide' : null ?>" data-control="preview-sidebar">
<!-- Right sidebar -->
<?= $this->makePartial('right-sidebar') ?>
</div>
</div>
</div>
<div class="layout-row min-size hide" data-control="bottom-toolbar">
<?= $this->makePartial('bottom-toolbar') ?>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->makePartial('new-folder-form') ?>
</div>

View File

@@ -0,0 +1,30 @@
<div class="panel no-padding-bottom border-top">
<div class="form-buttons">
<div class="pull-right">
<button
type="button"
data-command="popup-command"
data-popup-command="insert"
class="btn btn-primary">
<?= e(trans('backend::lang.media.insert')) ?>
</button>
<?php if (!$this->readOnly): ?>
<button
type="button"
data-command="popup-command"
data-popup-command="crop-and-insert"
class="btn btn-primary hide">
<?= e(trans('backend::lang.media.crop_and_insert')) ?>
</button>
<?php endif; ?>
<button
type="button"
data-dismiss="popup"
class="btn btn-default no-margin-right">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,33 @@
<div class="layout"
data-control="media-manager-crop-tool"
<?php if ($dimensions): ?>
data-image-width="<?= $dimensions[0] ?>"
data-image-height="<?= $dimensions[1] ?>"
<?php endif ?>
>
<div class="layout-row min-size ruler-container horizontal hide">
<div class="layout-cell">
<div class="layout-relative">
<div class="ruler" data-control="h-ruler"></div>
</div>
</div>
</div>
<div class="layout-row">
<div class="layout">
<div class="layout-row">
<div class="layout-cell ruler-container vertical hide">
<div class="layout-relative">
<div class="ruler" data-control="v-ruler"></div>
</div>
</div>
<div class="layout-cell">
<div class="layout-relative">
<div class="image_area">
<img src="<?= $imageUrl ?>"/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,40 @@
<?php
$selectionModes = [
Backend\Widgets\MediaManager::SELECTION_MODE_NORMAL => trans('backend::lang.media.selection_mode_normal'),
Backend\Widgets\MediaManager::SELECTION_MODE_FIXED_RATIO => trans('backend::lang.media.selection_mode_fixed_ratio'),
Backend\Widgets\MediaManager::SELECTION_MODE_FIXED_SIZE => trans('backend::lang.media.selection_mode_fixed_size')
];
$sizeDisabledAttr = $currentSelectionMode == Backend\Widgets\MediaManager::SELECTION_MODE_NORMAL ? 'disabled="disabled"' : null;
?>
<div class="control-toolbar toolbar-padded">
<div class="toolbar-item toolbar-primary">
<div data-control="toolbar">
<label class="standalone"><?= e(trans('backend::lang.media.image_size')) ?> <span data-label="width"><?= $dimensions[0] ?></span> x <span data-label="height"><?= $dimensions[1] ?></span></label>
<div class="btn-group offset-right">
<button type="button" class="btn btn-primary standalone" data-command="resize"
><?= e(trans('backend::lang.media.resize')) ?></button>
<button type="button" class="btn btn-primary wn-icon-undo empty" data-command="undo-resizing"></button>
</div>
<label for="mmcropimagewidth"><?= e(trans('backend::lang.media.selection_mode')) ?></label>
<select name="selectionMode" class="form-control custom-select w-150" data-control="selection-mode">
<?php foreach ($selectionModes as $mode => $name): ?>
<option <?= $mode == $currentSelectionMode ? 'selected="selected"' : null ?> value="<?= $mode ?>"><?= e($name) ?></option>
<?php endforeach ?>
</select>
<label for="mmcropimagewidth"><?= e(trans('backend::lang.media.width')) ?></label>
<input id="mmcropimagewidth" type="text" class="form-control w-100" data-control="crop-width-input" name="selectionWidth" value="<?= e($currentSelectionWidth) ?>" <?= $sizeDisabledAttr ?>/>
<label for="mmcropimageheight"><?= e(trans('backend::lang.media.height')) ?></label>
<input id="mmcropimageheight" type="text" class="form-control w-100" data-control="crop-height-input" name="selectionHeight" value="<?= e($currentSelectionHeight) ?>" <?= $sizeDisabledAttr ?>/>
<label class="standalone hide" data-label="selection-size"><?= e(trans('backend::lang.media.selected_size')) ?> <span data-label="selection-width"></span> x <span data-label="selection-height"></span></label>
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
<h3 class="section"><?= e(trans('backend::lang.media.display')) ?></h3>
<ul class="nav nav-stacked selector-group">
<li
role="presentation"
<?php if ($currentFilter == Backend\Widgets\MediaManager::FILTER_ALL): ?>
class="active"
<?php endif ?>
>
<a href="#" data-command="set-filter" data-filter="<?= Backend\Widgets\MediaManager::FILTER_ALL ?>">
<i class="icon-recycle"></i>
<?= e(trans('backend::lang.media.filter_all')) ?>
</a>
</li>
<li
role="presentation"
<?php if ($currentFilter == System\Classes\MediaLibraryItem::FILE_TYPE_IMAGE): ?>
class="active"
<?php endif ?>
>
<a href="#" data-command="set-filter" data-filter="<?= System\Classes\MediaLibraryItem::FILE_TYPE_IMAGE ?>">
<i class="icon-picture-o"></i>
<?= e(trans('backend::lang.media.filter_images')) ?>
</a>
</li>
<li
role="presentation"
<?php if ($currentFilter == System\Classes\MediaLibraryItem::FILE_TYPE_VIDEO): ?>
class="active"
<?php endif ?>
>
<a href="#" data-command="set-filter" data-filter="<?= System\Classes\MediaLibraryItem::FILE_TYPE_VIDEO ?>">
<i class="icon-video-camera"></i>
<?= e(trans('backend::lang.media.filter_video')) ?>
</a>
</li>
<li
role="presentation"
<?php if ($currentFilter == System\Classes\MediaLibraryItem::FILE_TYPE_AUDIO): ?>
class="active"
<?php endif ?>
>
<a href="#" data-command="set-filter" data-filter="<?= System\Classes\MediaLibraryItem::FILE_TYPE_AUDIO ?>">
<i class="icon-volume-up"></i>
<?= e(trans('backend::lang.media.filter_audio')) ?>
</a>
</li>
<li
role="presentation"
<?php if ($currentFilter == System\Classes\MediaLibraryItem::FILE_TYPE_DOCUMENT): ?>
class="active"
<?php endif ?>
>
<a href="#" data-command="set-filter" data-filter="<?= System\Classes\MediaLibraryItem::FILE_TYPE_DOCUMENT ?>">
<i class="icon-file"></i>
<?= e(trans('backend::lang.media.filter_documents')) ?>
</a>
</li>
</ul>

View File

@@ -0,0 +1,13 @@
<ul class="tree-path">
<li class="root"><a href="javascript:;" data-type="media-item" data-item-type="folder" data-path="/" data-clear-search="true"><?= e(trans('backend::lang.media.library')) ?></a></li>
<?php if (!$searchMode): ?>
<?php foreach ($pathSegments as $folder => $path): ?>
<?php if ($path != '/'): ?>
<li><a href="javascript:;" data-type="media-item" data-item-type="folder" data-path="<?= e($path) ?>"><?= basename($folder) ?></a></li>
<?php endif ?>
<?php endforeach?>
<?php else: ?>
<li><a href="javascript:;" data-type="media-item"><?= e(trans('backend::lang.media.search')) ?></a></li>
<?php endif ?>
</ul>

View File

@@ -0,0 +1,16 @@
<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"
data-command="toggle-sidebar"
class="wn-icon-sign-out btn-icon pull-right larger <?= !$sidebarVisible ? 'sidebar-hidden' : null ?>">
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,63 @@
<?php
$listElementId = $this->getId('item-list');
?>
<ul class="media-list <?= $listClass ?>">
<?php if (count($items) > 0 || !$isRootFolder): ?>
<?php if (!$isRootFolder && !$searchMode): ?>
<li tabindex="0" 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-arrow-turn-up"></i></div>
</div>
<div class="info">
<h4 title="<?= e(trans('backend::lang.media.return_to_parent')) ?>"><?= e(trans('backend::lang.media.return_to_parent_label')) ?></h4>
</div>
</li>
<?php endif ?>
<?php foreach ($items as $item): ?>
<?php
$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-size-bytes="<?= $item->size ?>"
data-last-modified="<?= e($item->lastModifiedAsString()) ?>"
data-last-modified-ts="<?= $item->lastModified ?>"
data-public-url="<?= e($item->publicUrl) ?>"
data-document-type="<?= e($itemType) ?>"
data-folder="<?= e(dirname($item->path)) ?>"
tabindex="0"
>
<?= $this->makePartial('item-icon', ['itemType'=>$itemType, 'item'=>$item]) ?>
<div class="info">
<h4 title="<?= e(basename($item->path)) ?>">
<?= e(basename($item->path)) ?>
<?php if (!$this->readOnly): ?>
<a
href="#"
data-rename
data-control="popup"
data-z-index="1200"
data-request-data="path: '<?= e($item->path) ?>', listId: '<?= $listElementId ?>', type: '<?= $item->type ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
><i data-rename-control class="icon-terminal"></i></a>
<?php endif; ?>
</h4>
<p class="size"><?= e($item->sizeToString()) ?></p>
</div>
</li>
<?php endforeach ?>
<?php endif ?>
<?php if (count($items) == 0 && $searchMode): ?>
<li class="no-data">
<?= e(trans('backend::lang.media.no_files_found')) ?>
</li>
<?php endif ?>
</ul>

View File

@@ -0,0 +1,42 @@
<?= Form::open(['class'=>'layout', 'onsubmit'=>'return false']) ?>
<div class="layout-row min-size">
<?= $this->makePartial('crop-toolbar') ?>
</div>
<div class="layout-row whiteboard">
<?= $this->makePartial('crop-tool-image-area') ?>
</div>
<div class="layout-row min-size whiteboard">
<div class="panel no-padding-bottom border-top">
<div class="form-buttons">
<div class="pull-right">
<button
type="button"
data-command="insert"
class="btn btn-primary">
<?= e(trans('backend::lang.media.crop_and_insert')) ?>
</button>
<button
type="button"
data-dismiss="popup"
class="btn btn-default no-margin-right">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
</div>
</div>
</div>
<input type="hidden" name="path" value="<?= e($path) ?>">
<input type="hidden" data-control="dimension-width" value="<?= $dimensions[0] ?>">
<input type="hidden" data-control="dimension-height" value="<?= $dimensions[1] ?>">
<input type="hidden" data-control="original-width" value="<?= $dimensions[0] ?>">
<input type="hidden" data-control="original-height" value="<?= $dimensions[1] ?>">
<input type="hidden" data-control="original-ratio" value="<?= $originalRatio ?>">
<input type="hidden" data-control="original-url" value="<?= e($imageUrl) ?>">
<?= $this->makePartial('resize-image-form') ?>
<?= Form::close() ?>

View File

@@ -0,0 +1,45 @@
<div class="icon-container <?= $itemType ?>">
<div class="icon-wrapper">
<?php
$itemIconClass = $this->itemTypeToIconClass($item, $itemType);
$extension = substr(strtolower(pathinfo($item->path, PATHINFO_EXTENSION)), 0, 4) ?? '???';
if ($itemIconClass == 'icon-file'): ?>
<svg class="file-icon" viewBox="0 0 250 250" xml:space="preserve">
<path d="M62.5,0c-8.594,0 -15.625,7.031 -15.625,15.625l0,218.75c0,8.594 7.031,15.625 15.625,15.625l156.25,0c8.594,0 15.625,-7.031 15.625,-15.625l0,-171.875l-62.5,-62.5l-109.375,0Z" style="fill:#e2e5e7;fill-rule:nonzero;"/>
<path d="M187.5,62.5l46.875,0l-62.5,-62.5l0,46.875c0,8.594 7.031,15.625 15.625,15.625Z" style="fill:#b0b7bd;fill-rule:nonzero;"/>
<path d="M234.375,109.375l-46.875,-46.875l46.875,0l0,46.875Z" style="fill:#cad1d8;fill-rule:nonzero;"/>
<path d="M195.313,210.938l-148.438,-0.001l0,7.813l148.438,0c4.296,0 7.812,-3.516 7.812,-7.813l-0,-7.812c-0,4.297 -3.516,7.813 -7.813,7.813Z" style="fill:#cad1d8;fill-rule:nonzero;"/>
<!-- Colour label -->
<path class="file-icon-label file-icon-<?= $extension ?>" d="M203.125,203.125c0,4.297 -3.516,7.813 -7.813,7.813l-171.875,-0c-4.296,-0 -7.812,-3.516 -7.812,-7.813l-0,-78.125c-0,-4.297 3.516,-7.813 7.812,-7.813l171.875,0.001c4.297,-0.001 7.813,3.515 7.813,7.812l0,78.125Z"/>
<!-- Extension text node -->
<text class="file-icon-extension" x="43%" y="170px" dominant-baseline="middle" text-anchor="middle"><?= strtoupper($extension) ?></text>
</svg>
<?php else :?>
<i class="<?= $itemIconClass ?>"></i>
<?php endif ?>
</div>
<?php if (
$itemType == System\Classes\MediaLibraryItem::FILE_TYPE_IMAGE
&& $thumbnailUrl = $this->getResizedImageUrl($item->path, $thumbnailParams)
): ?>
<div>
<?php if (!$thumbnailUrl): ?>
<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', [
'imageUrl' => $thumbnailUrl,
]) ?>
<?php endif ?>
</div>
<?php endif ?>
</div>

View File

@@ -0,0 +1,17 @@
<div class="panel no-padding padding-top">
<input type="hidden" data-type="current-folder" value="<?= e($currentFolder) ?>"/>
<input type="hidden" data-type="search-mode" value="<?= $searchMode ? 'true' : 'false' ?>"/>
<div class="list-container">
<?php if (count($items) == 0 && $isRootFolder && !$searchMode): ?>
<div class="empty-library"><?= e(trans('backend::lang.media.empty_library')) ?></div>
<?php endif ?>
<?php if ($viewMode == Backend\Widgets\MediaManager::VIEW_MODE_GRID): ?>
<?= $this->makePartial('list-grid') ?>
<?php elseif ($viewMode == Backend\Widgets\MediaManager::VIEW_MODE_LIST): ?>
<?= $this->makePartial('list-list') ?>
<?php else: ?>
<?= $this->makePartial('list-tiles') ?>
<?php endif ?>
</div>
</div>

View File

@@ -0,0 +1,50 @@
<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::asset('modules/backend/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="document-template">
<div class="sidebar-document-placeholder-container"><div class="sidebar-document-placeholder" data-path="{path}" data-last-modified="{last-modified}" data-control="sidebar-document"></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('backend::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('backend::lang.media.multiple_selected')) ?></p>
</div>
</div>
</script>
<script type="text/template" data-control="go-up">
<div class="sidebar-image-placeholder-container">
<div class="sidebar-image-placeholder no-border">
<i class="icon-level-up"></i>
<p><?= e(trans('backend::lang.media.return_to_parent')) ?></p>
</div>
</div>
</script>

View File

@@ -0,0 +1,7 @@
<?php if ($this->getFilterDisplay()): ?>
<div id="<?= $this->getId('filters') ?>">
<?= $this->makePartial('filters') ?>
</div>
<?php endif; ?>
<?= $this->makePartial('sorting') ?>

View File

@@ -0,0 +1,70 @@
<?php
$listElementId = $this->getId('item-list');
?>
<table class="table data">
<col />
<col width="130px" />
<col width="130px" />
<tbody class="icons clickable">
<?php if (count($items) > 0 || !$isRootFolder): ?>
<?php if (!$isRootFolder && !$searchMode): ?>
<tr data-type="media-item" data-item-type="folder" data-root data-path="<?= e(dirname($currentFolder)) ?>" tabindex="0">
<td><i class="icon-arrow-turn-up"></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-size-bytes="<?= $item->size ?>"
data-last-modified="<?= e($item->lastModifiedAsString()) ?>"
data-last-modified-ts="<?= $item->lastModified ?>"
data-public-url="<?= e($item->publicUrl) ?>"
data-document-type="<?= e($itemType) ?>"
data-folder="<?= e(dirname($item->path)) ?>"
tabindex="0"
>
<td>
<div class="item-title no-wrap-text">
<i class="<?= $this->itemTypeToIconClass($item, $itemType) ?>"></i> <?= e(basename($item->path)) ?>
<?php if (!$this->readOnly): ?>
<a
href="#"
data-rename
data-control="popup"
data-request-data="path: '<?= e($item->path) ?>', listId: '<?= $listElementId ?>', type: '<?= $item->type ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
data-z-index="1200"
><i data-rename-control class="icon-terminal"></i></a>
<?php endif; ?>
</div>
</td>
<td><?= e($item->sizeToString()) ?></td>
<td><?= e($item->lastModifiedAsString()) ?></td>
<?php if ($searchMode): ?>
<td title="<?= e(dirname($item->path)) ?>">
<div class="no-wrap-text"><?= e(dirname($item->path)) ?></div>
</td>
<?php endif ?>
</tr>
<?php endforeach ?>
<?php endif ?>
</tbody>
</table>
<?php if (count($items) == 0 && $searchMode): ?>
<p class="no-data">
<?= e(trans('backend::lang.media.no_files_found')) ?>
</p>
<?php endif ?>

View File

@@ -0,0 +1 @@
<?= $this->makePartial('generic-list', ['listClass' => 'list']) ?>

View File

@@ -0,0 +1 @@
<?= $this->makePartial('generic-list', ['listClass' => 'tiles']) ?>

View File

@@ -0,0 +1,35 @@
<?= Form::open() ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.media.move_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('backend::lang.media.move_destination')) ?></label>
<select
class="form-control custom-select"
name="dest"
data-placeholder="<?= e(trans('backend::lang.media.move_please_select')) ?>">
<option></option>
<?php foreach ($folders as $path => $folder): ?>
<option value="<?= e($path) ?>"><?= e($folder) ?></option>
<?php endforeach ?>
</select>
<input type="hidden" name="originalPath" value="<?= e($originalPath) ?>">
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.media.move_button')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
</form>

View File

@@ -0,0 +1,32 @@
<script type="text/template" data-control="new-folder-template">
<?= Form::open() ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.media.new_folder_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('backend::lang.media.folder_name')) ?></label>
<input
type="text"
class="form-control"
name="name"
value=""
autocomplete="off" />
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
</form>
</script>

View File

@@ -0,0 +1,3 @@
<?= Form::open(['class'=>'layout', 'onsubmit'=>'return false']) ?>
<?= $this->render() ?>
<?= Form::close() ?>

View File

@@ -0,0 +1,56 @@
<?= Form::ajax($this->getEventHandler('onApplyName'), [
'success' => "\$el.trigger('close.oc.popup'); \$('#".$listId."').trigger('mediarefresh');",
'data-stripe-load-indicator' => 1,
'id' => 'media-rename-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.media.rename_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('backend::lang.media.rename_new_name')) ?></label>
<input
type="text"
name="name"
value="<?= e($name) ?>"
class="form-control"
autocomplete="off"
default-focus />
</div>
<input type="hidden" name="originalName" value="<?= e($name) ?>">
<input type="hidden" name="type" value="<?= e($type) ?>">
<input type="hidden" name="originalPath" value="<?= e($originalPath) ?>" />
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<script>
setTimeout(
function(){ $('#media-rename-popup-form input.form-control').focus() },
310
)
$('#media-rename-popup-form').on('oc.beforeRequest', function(ev){
var originalName = $('#media-rename-popup-form [name=originalName]').val(),
newName = $.trim($('#media-rename-popup-form [name=name]').val())
if (originalName == newName || newName.length == 0) {
alert('Please enter a new name')
ev.preventDefault()
}
})
</script>
<?= Form::close() ?>

View File

@@ -0,0 +1,31 @@
<script type="text/template" data-control="resize-template">
<?= Form::open() ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.media.resize_image')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group span-left">
<label><?= e(trans('backend::lang.media.width')) ?></label>
<input type="text" class="form-control" name="width" value="" />
</div>
<div class="form-group span-right">
<label><?= e(trans('backend::lang.media.height')) ?></label>
<input type="text" class="form-control" name="height" value="" />
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
</form>
</script>

View File

@@ -0,0 +1,26 @@
<?= $this->makePartial('item-sidebar-preview') ?>
<div class="panel hide" data-control="sidebar-labels">
<label><?= e(trans('backend::lang.media.title')) ?></label>
<p data-label="title"></p>
<table class="name-value-list">
<tr>
<th><?= e(trans('backend::lang.media.size')) ?></th>
<td data-label="size"></td>
</tr>
<tr>
<th><?= e(trans('backend::lang.media.public_url')) ?></th>
<td><a href="#" data-label="public-url" target="_blank"><?= e(trans('backend::lang.media.click_here')) ?></a></td>
</tr>
<tr data-control="last-modified">
<th><?= e(trans('backend::lang.media.last_modified')) ?></th>
<td data-label="last-modified"></td>
</tr>
<tr data-control="item-folder" class="hide">
<th><?= e(trans('backend::lang.media.folder')) ?></th>
<td><a href="#" data-type="media-item" data-item-type="folder" data-label="folder" data-clear-search="true"></a></td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,46 @@
<?php
$sortModes = [
System\Classes\MediaLibrary::SORT_BY_TITLE => trans('backend::lang.media.title'),
System\Classes\MediaLibrary::SORT_BY_SIZE => trans('backend::lang.media.size'),
System\Classes\MediaLibrary::SORT_BY_MODIFIED => trans('backend::lang.media.last_modified')
];
$sortDirections = [
System\Classes\MediaLibrary::SORT_DIRECTION_ASC => trans('backend::lang.media.direction_asc'),
System\Classes\MediaLibrary::SORT_DIRECTION_DESC => trans('backend::lang.media.direction_desc')
];
?>
<div class="sidebar-group">
<h3 class="section"><?= e(trans('backend::lang.media.order_by')) ?></h3>
<select
name="sorting"
class="form-control custom-select select-no-search"
data-control="sorting"
data-sort="by">
<?php foreach ($sortModes as $code => $title): ?>
<option
<?= $code == $sortBy ? 'selected="selected"' : '' ?>
value="<?= $code ?>"
><?= e($title) ?></option>
<?php endforeach ?>
</select>
</div>
<div class="sidebar-group">
<h3 class="section"><?= e(trans('backend::lang.media.direction')) ?></h3>
<select
name="sorting"
class="form-control custom-select select-no-search"
data-control="sorting"
data-sort="direction">
<?php foreach ($sortDirections as $code => $title): ?>
<option
<?= $code == $sortDirection ? 'selected="selected"' : '' ?>
value="<?= $code ?>"
><?= e($title) ?></option>
<?php endforeach ?>
</select>
</div>

View File

@@ -0,0 +1,6 @@
<?php if ($imageUrl): ?>
<img src="<?= $imageUrl ?>"/>
<?php else: ?>
<i class="icon-chain-broken" title="<?= e(trans('backend::lang.media.thumbnail_error')) ?>"></i>
<p class="thumbnail-error-message"><?= e(trans('backend::lang.media.thumbnail_error')) ?></p>
<?php endif ?>

View File

@@ -0,0 +1,42 @@
<div class="layout-row min-size">
<div class="control-toolbar toolbar-padded">
<div class="toolbar-item toolbar-primary">
<div data-control="toolbar">
<?php if (!$this->readOnly): ?>
<div class="btn-group offset-right">
<button type="button" class="btn btn-primary wn-icon-upload" data-control="upload"><?= e(trans('backend::lang.media.upload')) ?></button>
<button type="button" class="btn btn-primary wn-icon-folder" data-command="create-folder"><?= e(trans('backend::lang.media.add_folder')) ?></button>
</div>
<?php endif; ?>
<button type="button" class="btn btn-default wn-icon-refresh empty offset-right" data-command="refresh"></button>
<?php if (!$this->readOnly): ?>
<div class="btn-group offset-right">
<button type="button" class="btn btn-default wn-icon-reply-all" data-command="move"><?= e(trans('backend::lang.media.move')) ?></button>
<button type="button" class="btn btn-default wn-icon-trash" data-command="delete"><?= e(trans('backend::lang.media.delete')) ?></button>
</div>
<?php endif; ?>
<div class="btn-group offset-right" id="<?= $this->getId('view-mode-buttons') ?>">
<?= $this->makePartial('view-mode-buttons') ?>
</div>
</div>
</div>
<div class="toolbar-item" data-calculate-width>
<div class="relative loading-indicator-container size-input-text">
<input
type="text"
name="search"
value="<?= e($searchTerm) ?>"
class="form-control icon search growable"
placeholder="<?= e(trans('backend::lang.media.search')) ?>"
data-control="search"
autocomplete="off"
data-load-indicator
data-load-indicator-opaque
/>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,24 @@
<div class="layout-row min-size hide" data-control="upload-ui">
<div class="layout">
<div class="upload-progress">
<h5
data-label="file-number-and-progress"
data-message-template="<?= e(trans('backend::lang.media.uploading_file_num')) ?> &lt;span&gt;:percents&lt;/span&gt;"
data-success-template="<?= e(trans('backend::lang.media.uploading_complete')) ?>"
data-error-template="<?= e(trans('backend::lang.media.uploading_error')) ?>"
></h5>
<div class="progress-controls">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0;" data-control="upload-progress-bar">
</div>
</div>
<div class="controls">
<a href="#" data-command="cancel-uploading"><i class="icon-times-circle" title=""></i></a>
<a class="hide" href="#" data-command="close-uploader"><i class="icon-check-circle" title=""></i></a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,18 @@
<button
type="button"
class="btn btn-default wn-icon-align-justify empty <?= $viewMode == Backend\Widgets\MediaManager::VIEW_MODE_GRID ? 'on' : '' ?>"
data-command="change-view"
data-view="<?= Backend\Widgets\MediaManager::VIEW_MODE_GRID ?>">
</button>
<button
type="button"
class="btn btn-default wn-icon-th empty <?= $viewMode == Backend\Widgets\MediaManager::VIEW_MODE_LIST ? 'on' : '' ?>"
data-command="change-view"
data-view="<?= Backend\Widgets\MediaManager::VIEW_MODE_LIST ?>">
</button>
<button
type="button"
class="btn btn-default wn-icon-th-large empty <?= $viewMode == Backend\Widgets\MediaManager::VIEW_MODE_TILES ? 'on' : '' ?>"
data-command="change-view"
data-view="<?= Backend\Widgets\MediaManager::VIEW_MODE_TILES ?>">
</button>

Some files were not shown because too many files have changed in this diff Show More