issue #1175 redesign plugin manager

* design based on Samuel's mockup + Hannah's adaptations
* on each plugin actions shown as buttons in a single column (better compatibility with verbose languages such as German)
* description always shown (no more need of the "show details" action)
* filter based on plugin title/description (javascript, no page reload)
* hide inactive plugins if they are 8 or more
* plugins no longer shown in the admin left menu, they instead get a "settings" action if relevant. Compatible with the old trigger get_admin_plugin_menu_links but new method is to simply add a "Has Setting : true" in the main.inc.php header
This commit is contained in:
Zacharieg
2020-04-22 14:41:52 +02:00
committed by GitHub
parent 669f3ff864
commit 0a268bcce1
7 changed files with 268 additions and 134 deletions

View File

@@ -261,19 +261,6 @@ if ($nb_orphans > 0)
);
}
// +-----------------------------------------------------------------------+
// | Plugin menu |
// +-----------------------------------------------------------------------+
$plugin_menu_links = trigger_change('get_admin_plugin_menu_links', array() );
function UC_name_compare($a, $b)
{
return strcmp(strtolower($a['NAME']), strtolower($b['NAME']));
}
usort($plugin_menu_links, 'UC_name_compare');
$template->assign('plugin_menu_items', $plugin_menu_links);
// +-----------------------------------------------------------------------+
// | Refresh permissions |
// +-----------------------------------------------------------------------+

View File

@@ -285,6 +285,7 @@ DELETE FROM '. PLUGINS_TABLE .'
'uri'=>'',
'description'=>'',
'author'=>'',
'hasSettings'=>false,
);
$plg_data = file_get_contents($path.'/main.inc.php', null, null, 0, 2048);
@@ -316,6 +317,10 @@ DELETE FROM '. PLUGINS_TABLE .'
{
$plugin['author uri'] = trim($val[1]);
}
if (preg_match("/Has Settings:\\s*(true|True)/", $plg_data, $val))
{
$plugin['hasSettings'] = true;
}
if (!empty($plugin['uri']) and strpos($plugin['uri'] , 'extension_view.php?eid='))
{
list( , $extension) = explode('extension_view.php?eid=', $plugin['uri']);

View File

@@ -83,6 +83,21 @@ if (isset($_GET['incompatible_plugins']))
exit;
}
//--------------------------------------------------------Get the menu with the depreciated version
$plugin_menu_links_deprec = trigger_change('get_admin_plugin_menu_links', array());
$settings_url_for_plugin_deprec = array();
foreach ($plugin_menu_links_deprec as $value)
{
if (preg_match('/^admin\.php\?page=plugin-(.*)$/', $value["URL"], $matches)) {
$settings_url_for_plugin_deprec[$matches[1]] = $value["URL"];
} elseif (preg_match('/^.*section=(.*)[\/&%].*$/', $value["URL"], $matches)) {
$settings_url_for_plugin_deprec[$matches[1]] = $value["URL"];
}
}
// +-----------------------------------------------------------------------+
// | start template output |
// +-----------------------------------------------------------------------+
@@ -91,7 +106,7 @@ $plugins->sort_fs_plugins('name');
$merged_extensions = $plugins->get_merged_extensions();
$merged_plugins = false;
$tpl_plugins = array();
$active_plugins = 0;
$count_types_plugins = array("active"=>0, "inactive"=>0, "missing"=>0, "merged"=>0);
foreach($plugins->fs_plugins as $plugin_id => $fs_plugin)
{
@@ -102,6 +117,13 @@ foreach($plugins->fs_plugins as $plugin_id => $fs_plugin)
unset($_SESSION['incompatible_plugins']);
}
$setting_url = '';
if (isset($settings_url_for_plugin_deprec[$plugin_id])) { //old version
$setting_url = $settings_url_for_plugin_deprec[$plugin_id];
} else if ($fs_plugin['hasSettings']) { // new version
$setting_url = "admin.php?page=plugin-".$plugin_id;
}
$tpl_plugin = array(
'ID' => $plugin_id,
'NAME' => $fs_plugin['name'],
@@ -111,6 +133,7 @@ foreach($plugins->fs_plugins as $plugin_id => $fs_plugin)
'AUTHOR' => $fs_plugin['author'],
'AUTHOR_URL' => @$fs_plugin['author uri'],
'U_ACTION' => sprintf($action_url, $plugin_id),
'SETTINGS_URL' => $setting_url,
);
if (isset($plugins->db_plugins_by_id[$plugin_id]))
@@ -133,10 +156,7 @@ foreach($plugins->fs_plugins as $plugin_id => $fs_plugin)
$merged_plugins = true;
}
if ($tpl_plugin['STATE'] == 'active')
{
$active_plugins++;
}
$count_types_plugins[$tpl_plugin['STATE']]++;
$tpl_plugins[] = $tpl_plugin;
}
@@ -184,7 +204,7 @@ usort($tpl_plugins, 'cmp');
$template->assign(
array(
'plugins' => $tpl_plugins,
'active_plugins' => $active_plugins,
'count_types_plugins' => $count_types_plugins,
'PWG_TOKEN' => $pwg_token,
'base_url' => $base_url,
'show_details' => $show_details,

View File

@@ -91,14 +91,6 @@ jQuery(document).ready(function() {
<ul>
<li><a href="{$U_PLUGINS}"><i class="icon-equalizer"></i>{'Manage'|@translate}</a></li>
</ul>
{if !empty($plugin_menu_items)}
<div id="pluginsMenuSeparator"></div>
<ul class="scroll">
{foreach from=$plugin_menu_items item=menu_item}
<li><a href="{$menu_item.URL}">{$menu_item.NAME}</a></li>
{/foreach}
</ul>
{/if}
</dd>
</dl>
<dl>

View File

@@ -5,6 +5,12 @@
var incompatible_msg = '{'WARNING! This plugin does not seem to be compatible with this version of Piwigo.'|@translate|@escape:'javascript'}';
var activate_msg = '\n{'Do you want to activate anyway?'|@translate|@escape:'javascript'}';
var showInactivePlugins = function() {
jQuery(".showInactivePlugins").fadeOut(complete=function(){
jQuery(".plugin-inactive").fadeIn();
})
}
/* group action */
var pwg_token = '{$PWG_TOKEN}';
var confirmMsg = '{'Are you sure?'|@translate|@escape:'javascript'}';
@@ -82,6 +88,76 @@ jQuery(document).ready(function() {
'keepAlive':true,
'activation':'click'
});
jQuery('.fullInfo').tipTip({
'delay' : 500,
'fadeIn' : 200,
'fadeOut' : 200,
'maxWidth':'300px',
'keepAlive':false,
});
/* Add the '...' for the overflow of the description line*/
jQuery( document ).ready(function () {
jQuery('.pluginDesc').each(function () {
var el = jQuery(this).context;
var wordArray = el.innerHTML.split(' ');
if (el.scrollHeight > el.offsetHeight) {
jQuery(this).attr('title', jQuery(this).html())
}
while(el.scrollHeight > el.offsetHeight) {
wordArray.pop();
el.innerHTML = wordArray.join(' ') + '...';
}
})
});
/*Add the filter research*/
jQuery( document ).ready(function () {
jQuery(".pluginFilter input").on("input", function() {
let text = jQuery(this).val().toLowerCase();
var searchNumber = 0;
jQuery('.pluginBoxes').each(function () {
let searchNumberInBox = 0;
let pluginBoxes = jQuery(this);
pluginBoxes.find(".pluginMiniBox").each(function() {
if (text == "") {
jQuery(this).fadeIn()
searchNumberInBox++;
} else {
let name = jQuery(this).find(".pluginMiniBoxNameCell").text().toLowerCase();
let description = jQuery(this).find(".pluginDesc").text().toLowerCase();
if (name.search(text) != -1 || description.search(text) != -1){
jQuery(this).fadeIn()
searchNumberInBox++;
} else {
jQuery(this).fadeOut()
}
}
})
if (searchNumberInBox == 0) {
pluginBoxes.fadeOut();
} else {
if (pluginBoxes.hasClass("plugin-inactive")) {
showInactivePlugins()
} else {
pluginBoxes.fadeIn();
}
}
searchNumber += searchNumberInBox;
});
console.log(searchNumber);
if (searchNumber == 0) {
jQuery(".emptyResearch").fadeIn();
} else {
jQuery(".emptyResearch").fadeOut();
}
});
});
/* Show Inactive plugins or button to show them*/
jQuery( document ).ready(function () {
jQuery(".showInactivePlugins button").on('click', showInactivePlugins)
});
});
{/literal}
{/footer_script}
@@ -90,36 +166,45 @@ jQuery(document).ready(function() {
<h2>{'Plugins'|@translate}</h2>
</div>
<div class="showDetails">
{if $show_details}
<a href="{$base_url}&amp;show_details=0">{'hide details'|@translate}</a>
{else}
<a href="{$base_url}&amp;show_details=1">{'show details'|@translate}</a>
{/if}
</div>
{if isset($plugins)}
{assign var='field_name' value='null'} {* <!-- 'counter' for fieldset management --> *}
{counter start=0 assign=i} {* <!-- counter for 'deactivate all' link --> *}
<div class="pluginFilter">
<p class="icon-filter">{'Filter'|@translate}</p>
<input type="text" placeholder="{'Name'|@translate}, {'Description'|@translate}">
</div>
<div class="emptyResearch"> {'No plugins found'|@translate} </div>
{foreach from=$plugins item=plugin name=plugins_loop}
{if $field_name != $plugin.STATE}
{if $field_name != 'null'}
</fieldset>
{/if}
{/if}
<fieldset class="pluginBoxes">
<fieldset class="pluginBoxes plugin-{$plugin.STATE}" {if $plugin.STATE == 'inactive'}{if $count_types_plugins["inactive"]>8}style="display:none"{/if}{/if}>
<legend>
{if $plugin.STATE == 'active'}
{'Active Plugins'|@translate}
{elseif $plugin.STATE == 'inactive'}
{'Inactive Plugins'|@translate}
{elseif $plugin.STATE == 'missing'}
{'Missing Plugins'|@translate}
{elseif $plugin.STATE == 'merged'}
{'Obsolete Plugins'|@translate}
{/if}
<div class="pluginBoxesTitle">
<p>
{if $plugin.STATE == 'active'}
{'Active Plugins'|@translate}
{elseif $plugin.STATE == 'inactive'}
{'Inactive Plugins'|@translate}
{elseif $plugin.STATE == 'missing'}
{'Missing Plugins'|@translate}
{elseif $plugin.STATE == 'merged'}
{'Obsolete Plugins'|@translate}
{/if}
</p>
<div class="pluginBoxesCount">{$count_types_plugins[$plugin.STATE]}</div>
</div>
{if $plugin.STATE == 'active'}
<div class="deactivate_all"><a>{'Deactivate all'|@translate}</a></div>
{/if}
</legend>
{assign var='field_name' value=$plugin.STATE}
{/if}
@@ -131,91 +216,62 @@ jQuery(document).ready(function() {
{assign var='author' value='<u>'|cat:$plugin.AUTHOR|cat:'</u>'}
{/if}
{/if}
{if $show_details}
<div id="{$plugin.ID}" class="pluginBox {$plugin.STATE}">
<table>
<tr>
<td class="pluginBoxNameCell">
{$plugin.NAME}
</td>
<td>{$plugin.DESC}</td>
</tr>
<tr class="pluginActions">
<td>
{if $plugin.STATE == 'active'}
<a href="{$plugin.U_ACTION}&amp;action=deactivate">{'Deactivate'|@translate}</a>
| <a href="{$plugin.U_ACTION}&amp;action=restore" class="plugin-restore" title="{'Restore default configuration. You will lose your plugin settings!'|@translate}" onclick="return confirm(confirmMsg);">{'Restore'|@translate}</a>
{elseif $plugin.STATE == 'inactive'}
<a href="{$plugin.U_ACTION}&amp;action=activate" class="activate">{'Activate'|@translate}</a>
| <a href="{$plugin.U_ACTION}&amp;action=delete" onclick="return confirm(confirmMsg);">{'Delete'|@translate}</a>
{elseif $plugin.STATE == 'missing'}
<a href="{$plugin.U_ACTION}&amp;action=uninstall" onclick="return confirm(confirmMsg);">{'Uninstall'|@translate}</a>
{elseif $plugin.STATE == 'merged'}
<a href="{$plugin.U_ACTION}&amp;action=delete">{'Delete'|@translate}</a>
{/if}
</td>
<td>
{'Version'|@translate} {$plugin.VERSION}
{if not empty($author)}
| {'By %s'|@translate:$author}
{/if}
{if not empty($plugin.VISIT_URL)}
| <a class="externalLink" href="{$plugin.VISIT_URL}">{'Visit plugin site'|@translate}</a>
{/if}
</td>
</tr>
</table>
</div> {*<!-- pluginBox -->*}
{if not empty($plugin.VISIT_URL)}
{assign var='version' value="<a class='externalLink' href='"|cat:$plugin.VISIT_URL|cat:"'>"|cat:$plugin.VERSION|cat:"</a>"}
{else}
{if not empty($plugin.VISIT_URL)}
{assign var='version' value="<a class='externalLink' href='"|cat:$plugin.VISIT_URL|cat:"'>"|cat:$plugin.VERSION|cat:"</a>"}
{else}
{assign var='version' value=$plugin.VERSION}
{/if}
<div id="{$plugin.ID}" class="pluginMiniBox {$plugin.STATE}">
{assign var='version' value=$plugin.VERSION}
{/if}
<div id="{$plugin.ID}" class="pluginMiniBox {$plugin.STATE}">
<div class="pluginBar {if $plugin.STATE == 'active'}pluginBarActive{else}pluginBarInactive{/if}" {if $plugin.STATE == 'active'}#ffa646{else}grey{/if}></div>
<div class="pluginContent">
<a class="icon-info-circled-1 showInfo" title="{if !empty($author)}{'By %s'|@translate:$author} | {/if}{'Version'|@translate} {$version}"></a>
<div class="pluginMiniBoxNameCell">
{$plugin.NAME}
<a class="icon-info-circled-1 showInfo" title="{if !empty($author)}{'By %s'|@translate:$author} | {/if}{'Version'|@translate} {$version}<br/>{$plugin.DESC|@escape:'html'}"></a>
</div>
<div class="pluginDesc">
{$plugin.DESC}
</div>
<div class="pluginActions">
<div>
{if $plugin.STATE == 'active'}
<a href="{$plugin.U_ACTION}&amp;action=deactivate">{'Deactivate'|@translate}</a>
| <a href="{$plugin.U_ACTION}&amp;action=restore" class="plugin-restore" title="{'Restore default configuration. You will lose your plugin settings!'|@translate}" onclick="return confirm(confirmMsg);">{'Restore'|@translate}</a>
{if $plugin.SETTINGS_URL != ''}
<a href="{$plugin.SETTINGS_URL}" class="pluginActionLevel1 icon-cog">{'Settings'|@translate}</a>
{else}
<div class="pluginUnavailableAction icon-cog">{'Settings'|@translate}</div>
{/if}
<a class="pluginActionLevel2 icon-cancel-circled" href="{$plugin.U_ACTION}&amp;action=deactivate">{'Deactivate'|@translate}</a>
<a class="pluginActionLevel3 icon-back-in-time" href="{$plugin.U_ACTION}&amp;action=restore" class="plugin-restore" title="{'Restore default configuration. You will lose your plugin settings!'|@translate}" onclick="return confirm(confirmMsg);">{'Restore'|@translate}</a>
{elseif $plugin.STATE == 'inactive'}
<a href="{$plugin.U_ACTION}&amp;action=activate" class="activate">{'Activate'|@translate}</a>
| <a href="{$plugin.U_ACTION}&amp;action=delete" onclick="return confirm(confirmMsg);">{'Delete'|@translate}</a>
<div class="pluginEmptyInput"></div>
<a class="pluginActionLevel1 icon-plus" href="{$plugin.U_ACTION}&amp;action=activate" class="activate">{'Activate'|@translate}</a>
<a class="pluginActionLevel3" href="{$plugin.U_ACTION}&amp;action=delete" onclick="return confirm(confirmMsg);">{'Delete'|@translate}</a>
{elseif $plugin.STATE == 'missing'}
<a href="{$plugin.U_ACTION}&amp;action=uninstall" onclick="return confirm(confirmMsg);">{'Uninstall'|@translate}</a>
<div class="pluginEmptyInput"></div>
<div class="pluginEmptyInput"></div>
<a class="pluginActionLevel3" href="{$plugin.U_ACTION}&amp;action=uninstall" onclick="return confirm(confirmMsg);">{'Uninstall'|@translate}</a>
{elseif $plugin.STATE == 'merged'}
<a href="{$plugin.U_ACTION}&amp;action=delete">{'Delete'|@translate}</a>
{/if}
</div>
<div class="pluginEmptyInput"></div>
<div class="pluginEmptyInput"></div>
<a class="pluginActionLevel3" href="{$plugin.U_ACTION}&amp;action=delete">{'Delete'|@translate}</a>
{/if}
</div>
</div> {*<!-- pluginMiniBox -->*}
</div>
</div> {*<!-- pluginMiniBox -->*}
{/if}
{if $plugin.STATE == 'active'}
{counter}
{if $active_plugins == $i}
<div class="deactivate_all"><a>{'Deactivate all'|@translate}</a></div>
{counter}
{/if}
{/if}
{/foreach}
{/foreach}
</fieldset>
<div class="showInactivePlugins" {if $count_types_plugins["inactive"]<=8}style="display:none"{/if} >
<div class="showInactivePluginsInfo">
{assign var='badge_inactive' value='<span class="pluginBoxesCount">%s</span>'|@sprintf:$count_types_plugins["inactive"]}
<div>{'You have %s inactive plugins'|translate:$badge_inactive}</div>
</div>
<button class="buttonLike" id="showInactivePluginsAction">{'Show inactive plugins'|@translate}</button>
</div>
{/if}

View File

@@ -1076,8 +1076,36 @@ h2:lang(en) { text-transform:capitalize; }
.checkActions {text-align:left;padding:0;margin:0;}
.pluginBoxes {
border: none;
text-align:left;
position:relative;
display: flex;
flex-wrap: wrap;
justify-content: left;
padding: 0;
min-height: 271px;
}
.pluginBoxesTitle {font-size: 16px;}
.pluginBoxesTitle p {margin-right: 10px;}
.pluginFilter,.pluginBoxesTitle {display: flex;align-items: center}
.pluginFilter p{margin-right: 10px}
.pluginFilter input {padding: 3px}
.pluginFilter {justify-content: end;position: absolute;right: 20px}
.pluginBoxesCount {
background-color: rgb(150, 150, 150);
color: white;
border-radius: 30%;
text-align: center;
padding-left: 3px;
padding-right: 3px;
height: 20px;
font-weight: bold;
font-size: 14px;
width: 25px;
}
.pluginBox {
@@ -1092,28 +1120,70 @@ h2:lang(en) { text-transform:capitalize; }
.pluginBox td.pluginDesc img {vertical-align:middle;}
.pluginBoxNameCell {width:180px; vertical-align:top;}
.pluginBoxes .inactive, .pluginBoxes .uninstalled {opacity:0.8;filter:alpha(opacity=80);}
.emptyResearch {opacity: 0.3;text-align: center;left: 45%;font-weight: bold;font-size: 30px; position: absolute; display: none;}
.pluginMiniBox {
display:inline-table;
text-align:center;
width:250px;
height:40px;
margin:5px;
border-width:1px;
border-style:solid;
border-radius:5px;
-moz-border-radius:5px;
display:flex;
box-shadow: 1px 1px 1px 1px #d7d7d7;
width:260px;
height:205px;
margin:10px;
border-radius:2px;
-moz-border-radius:2px;
overflow:hidden;
}
.pluginMiniBoxNameCell {font-size:1.05em; margin:5px 0;position:relative;}
.pluginActions {display: table-row; font-size:0.95em; color:#777;}
.pluginActions DIV {display: table-cell; vertical-align: middle; line-height:18px; }
.pluginMiniBox .showInfo, .themeBox .showInfo { display:block;position:absolute;top:0;right:5px;width:15px; }
.pluginBar {width: 1.5%;}
.pluginContent {
width: 98.5%;
text-align: left;
background-color: #fafafa;
padding: 7px;
padding-top: 10px;
padding-left: 10px;
overflow: hidden;
position: relative;
}
.pluginMiniBoxNameCell {
font-size: 14px;
font-weight: bold;
}
.pluginDesc {
margin-top: 5px;
font-size: 12px;
color: grey;
line-height: 1.5em;
height: 3em;
position: relative;
overflow: hidden;
}
.pluginActions {display: flex; font-size:11px; justify-content: space-between; margin-top: 1; bottom: 4px;position: absolute;flex-direction: column; align-items: center; width: 240px;}
.pluginActions > * {padding: 8px; text-align: center; padding-left: 4px; padding-right: 4px; width: 96%; margin: 4px}
.pluginEmptyInput {width: 90px;}
.pluginUnavailableAction {opacity: 0.5;}
.pluginActionLevel1 {background-color: #ffc17e; font-weight: bold;}
.pluginActionLevel1:hover {background-color: #ff7700; color: white; text-decoration: none;}
.pluginActionLevel2 {background-color: #ececec; font-weight: bold;}
.pluginActionLevel2:hover {background-color: #8b8b8b; color: white; text-decoration: none; }
.pluginActionLevel3 {padding-left: 10px; opacity: 0.9;}
.pluginBarActive {background-color: #ffc17e}
.pluginBarInactive {background-color: #8b8b8b}
.pluginMiniBox .showInfo, .themeBox .showInfo { display:block;position:absolute;top:0;right:5px;width:15px; padding: 10px;}
.warning:before {content:url(icon/warning.png);vertical-align:top;}
.deactivate_all {text-align:right;font-size:0.95em;}
.deactivate_all {font-size: 11px;font-weight: normal; margin-top: -5px; margin-bottom: 5px; font-weight: bold;opacity: 0.6;}
.showDetails {text-align:right; margin-bottom:-15px; margin-top:-10px; padding-right:1em;}
.showInactivePlugins {margin-bottom: 40px;}
.showInactivePluginsInfo {display: flex;justify-content: center;align-items: center; font-size: 18px;}
.showInactivePluginsInfo > * {margin: 5px;}
.showInactivePlugins button {font-size: 16px;}
.languageBoxes {min-height:0;text-align:left;}
.languageBox {display:inline-table; text-align:center; width:200px; height:40px; margin:5px; -moz-border-radius:5px;border-radius:5px; overflow:hidden; }
.languageName {font-size:1.1em; margin:5px 0;}

View File

@@ -72,7 +72,7 @@ h1 {
.content dl, dd { margin:5px; }
UL.thumbnails span.wrap2:hover { background-color#7CBA0F; color:#666; }
UL.thumbnails span.wrap2:hover { background-color: #7CBA0F; color:#666; }
UL.thumbnails span.wrap2 {
background-color:#333;
}
@@ -259,10 +259,14 @@ a.stat-box:hover {
#helpContent, #pLoaderPage, #ftpPage, #ftpPage LEGEND {color:#aaa;}
.pluginBox, .pluginMiniBox, .groups li {background-color:#333;color:#999;border-color:#333;}
.pluginBox, .pluginContent, .pluginMiniBox, .groups li {background-color:#333;color:#999;border-color:#333; box-shadow: none;}
.pluginBoxNameCell, .pluginMiniBoxNameCell {color:#ddd;}
.pluginBox.incompatible, .pluginMiniBox.incompatible {border-color:#800 !important;}
.pluginBoxes .merged, .pluginBoxes .missing {background-color:#422;border:1px solid #800;}
.pluginActionLevel1, .pluginActionLevel2 {color: #333;}
.pluginActionLevel2 {background-color: #bbbbbb;}
.pluginFilter {color: #c1c1c1;}
.pluginFilter input {border-color: #c1c1c1;}
.languageBox {background-color:#333;}
.languageName {color:#ccc;}
.languageDefault {background-color:#555; color:#aaa;}