Issue #1200 : Implementation of auto-order of categories and design fixes

This commit is contained in:
Zacharie
2020-08-03 14:23:24 +02:00
committed by plegall
parent 3b1c929e5c
commit 74447192b0
5 changed files with 462 additions and 59 deletions
+198 -24
View File
@@ -19,29 +19,95 @@ include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
check_status(ACCESS_ADMINISTRATOR);
// +-----------------------------------------------------------------------+
// | functions |
// | categories auto order |
// +-----------------------------------------------------------------------+
$open_cat = -1;
// +-----------------------------------------------------------------------+
// | categories movement |
// +-----------------------------------------------------------------------+
$sort_orders = array(
'name ASC',
'name DESC',
'date_creation DESC',
'date_creation ASC',
'date_available DESC',
'date_available ASC'
);
if (isset($_POST['submit']))
if (isset($_POST['simpleAutoOrder']) || isset($_POST['recursiveAutoOrder']) )
{
if (count($_POST['selection']) > 0)
{
check_input_parameter('selection', $_POST, true, PATTERN_ID);
check_input_parameter('parent', $_POST, false, PATTERN_ID);
move_categories($_POST['selection'], $_POST['parent']);
}
else
if (!in_array($_POST['order'],$sort_orders))
{
$page['errors'][] = l10n('Select at least one album');
die('Invalid sort order');
}
$query = '
SELECT id
FROM '.CATEGORIES_TABLE.'
WHERE id_uppercat '.
(($_POST['id'] === '-1') ? 'IS NULL' : '= '.$_POST['id']).'
;';
$category_ids = array_from_query($query, 'id');
if (isset($_POST['recursiveAutoOrder']))
{
$category_ids = get_subcat_ids($category_ids);
}
$categories = array();
$sort = array();
list($order_by_field, $order_by_asc) = explode(' ', $_POST['order']);
$order_by_date = false;
if (strpos($order_by_field, 'date_') === 0)
{
$order_by_date = true;
$ref_dates = get_categories_ref_date(
$category_ids,
$order_by_field,
'ASC' == $order_by_asc ? 'min' : 'max'
);
}
$query = '
SELECT id, name, id_uppercat
FROM '.CATEGORIES_TABLE.'
WHERE id IN ('.implode(',', $category_ids).')
;';
$result = pwg_query($query);
while ($row = pwg_db_fetch_assoc($result))
{
if ($order_by_date)
{
$sort[] = $ref_dates[ $row['id'] ];
}
else
{
$sort[] = remove_accents($row['name']);
}
$categories[] = array(
'id' => $row['id'],
'id_uppercat' => $row['id_uppercat'],
);
}
array_multisort(
$sort,
SORT_REGULAR,
'ASC' == $order_by_asc ? SORT_ASC : SORT_DESC,
$categories
);
save_categories_order($categories);
$open_cat = $_POST['id'];
}
$template->assign('open_cat', $open_cat);
// +-----------------------------------------------------------------------+
// | template initialization |
// +-----------------------------------------------------------------------+
@@ -65,30 +131,138 @@ include(PHPWG_ROOT_PATH.'admin/include/albums_tab.inc.php');
// | Album display |
// +-----------------------------------------------------------------------+
//Get all albums
$query = '
SELECT id,name,global_rank,status
SELECT id,name,rank,status, uppercats
FROM '.CATEGORIES_TABLE.'
;';
$allAlbum = query2array($query);
$sortedAlbums = array();
foreach ($allAlbum as $album) {
$parents = explode('.',$album['global_rank']);
$the_place = &$sortedAlbums[intval($parents[0])];
for ($i=1; $i < count($parents); $i++) {
$the_place = &$the_place['children'][intval($parents[$i])];
//Make an id tree
$associatedTree = array();
foreach ($allAlbum as $album)
{
$parents = explode(',',$album['uppercats']);
$the_place = &$associatedTree[strval($parents[0])];
for ($i=1; $i < count($parents); $i++)
{
$the_place = &$the_place['children'][strval($parents[$i])];
}
$the_place['name'] = $album['name'];
$the_place['status'] = $album['status'];
$the_place['id'] = $album['id'];
$the_place['cat'] = $album;
}
$template->assign('album_data', $sortedAlbums);
//Make an ordered tree
function cmpCat($a, $b)
{
if ($a['rank'] == $b['rank'])
{
return 0;
}
return ($a['rank'] < $b['rank']) ? -1 : 1;
}
function assocToOrderedTree($assocT)
{
$orderedTree = array();
foreach($assocT as $cat)
{
$orderedCat = array();
$orderedCat['rank'] = $cat['cat']['rank'];
$orderedCat['name'] = $cat['cat']['name'];
$orderedCat['status'] = $cat['cat']['status'];
$orderedCat['id'] = $cat['cat']['id'];
if (isset($cat['children']))
{
$orderedCat['children'] = assocToOrderedTree($cat['children']);
}
array_push($orderedTree, $orderedCat);
}
usort($orderedTree, 'cmpCat');
return $orderedTree;
}
$template->assign('album_data', assocToOrderedTree($associatedTree));
$template->assign('PWG_TOKEN', get_pwg_token());
// +-----------------------------------------------------------------------+
// | sending html code |
// +-----------------------------------------------------------------------+
$template->assign_var_from_handle('ADMIN_CONTENT', 'cat_move');
// +-----------------------------------------------------------------------+
// | functions |
// +-----------------------------------------------------------------------+
function get_categories_ref_date($ids, $field='date_available', $minmax='max')
{
// we need to work on the whole tree under each category, even if we don't
// want to sort sub categories
$category_ids = get_subcat_ids($ids);
// search for the reference date of each album
$query = '
SELECT
category_id,
'.$minmax.'('.$field.') as ref_date
FROM '.IMAGE_CATEGORY_TABLE.'
JOIN '.IMAGES_TABLE.' ON image_id = id
WHERE category_id IN ('.implode(',', $category_ids).')
GROUP BY category_id
;';
$ref_dates = query2array($query, 'category_id', 'ref_date');
// the iterate on all albums (having a ref_date or not) to find the
// reference_date, with a search on sub-albums
$query = '
SELECT
id,
uppercats
FROM '.CATEGORIES_TABLE.'
WHERE id IN ('.implode(',', $category_ids).')
;';
$uppercats_of = query2array($query, 'id', 'uppercats');
foreach (array_keys($uppercats_of) as $cat_id)
{
// find the subcats
$subcat_ids = array();
foreach ($uppercats_of as $id => $uppercats)
{
if (preg_match('/(^|,)'.$cat_id.'(,|$)/', $uppercats))
{
$subcat_ids[] = $id;
}
}
$to_compare = array();
foreach ($subcat_ids as $id)
{
if (isset($ref_dates[$id]))
{
$to_compare[] = $ref_dates[$id];
}
}
if (count($to_compare) > 0)
{
$ref_dates[$cat_id] = 'max' == $minmax ? max($to_compare) : min($to_compare);
}
else
{
$ref_dates[$cat_id] = null;
}
}
// only return the list of $ids, not the sub-categories
$return = array();
foreach ($ids as $id)
{
$return[$id] = $ref_dates[$id];
}
return $return;
}
?>
+56 -24
View File
@@ -1,8 +1,6 @@
$(document).ready(() => {
formatedData = $.map(data, function(value, index) {
return [formatInArray(value)];
});
formatedData = data;
$('.tree').tree({
data: formatedData,
@@ -23,11 +21,11 @@ $(document).ready(() => {
+"<a class='move-cat-edit icon-pencil' href='admin.php?page=album-"+node.id+"'>"+str_edit+"</a>"
+"</div>"
+'</div>';
actions_tree ="<a class='move-cat-manage icon-cog'>"+str_manage_sub_album+"</a>"
+"<a class='move-cat-order icon-sort-alt-up'>"+str_apply_order+"</a>";
action_order = "<a data-id='"+node.id+"' class='move-cat-order icon-sort-alt-up'>"+str_apply_order+"</a>";
cont = li.find('.jqtree-element')
cont.addClass('move-cat-container')
cont = li.find('.jqtree-element');
cont.addClass('move-cat-container');
cont.attr('id', 'cat-'+node.id)
cont.html('');
cont.append($(icon.replace(/%icon%/g, 'icon-ellipsis-vert')));
@@ -37,9 +35,9 @@ $(document).ready(() => {
cont.append($(icon.replace(/%icon%/g, 'icon-folder-open')));
}
cont.append($(title.replace(/%name%/g, node.name)))
cont.append($(title.replace(/%name%/g, node.name)));
if (node.status == 'private') {
cont.append($(icon.replace(/%icon%/g, 'icon-lock')))
cont.append($(icon.replace(/%icon%/g, 'icon-lock')));
}
cont.append(actions);
@@ -55,18 +53,15 @@ $(document).ready(() => {
.replace(/%content%/g, toggler)
.replace(/%id%/g, node.id)));
cont.find('.move-cat-action').append(actions_tree);
cont.find('.move-cat-action').append(action_order);
}
var colors = ["icon-red", "icon-blue", "icon-yellow", "icon-purple", "icon-green"];
var colorId = Number(node.id)%5;
cont.find(".icon-folder-open, .icon-flow-tree").addClass(colors[colorId]);
}
$('.tree').on( 'click', '.move-cat-toogler', function(e) {
console.log('clic');
var node_id = $(this).attr('data-id');
var node = $('.tree').tree('getNodeById', node_id);
if (node) {
@@ -116,7 +111,7 @@ $(document).ready(() => {
text: str_yes_change_parent,
btnClass: 'btn-red',
action: function () {
event.move_info.moved_node.status = 'private';
makePrivateHierarchy(event.move_info.moved_node);
applyMove(event);
},
},
@@ -134,16 +129,32 @@ $(document).ready(() => {
}
}
);
});
function formatInArray(obj) {
if (obj['children'] != null) {
obj['children'] = $.map(obj['children'], function(value, index) {
return [formatInArray(value)];
});
$('.tree').on( 'click', '.move-cat-order', function(e) {
var node_id = $(this).attr('data-id');
var node = $('.tree').tree('getNodeById', node_id);
console.log(node);
if (node) {
$('.cat-move-order-popin').fadeIn();
$('.cat-move-order-popin .album-name').html(getPathNode(node));
$('.cat-move-order-popin input[name=id]').val(node_id);
}
});
$('.order-root').on( 'click', function() {
$('.cat-move-order-popin').fadeIn();
$('.cat-move-order-popin .album-name').html(str_root);
$('.cat-move-order-popin input[name=id]').val(-1);
});
if (openCat != -1) {
var node = $('.tree').tree('getNodeById', openCat);
$('.tree').tree('openNode', node);
$([document.documentElement, document.body]).animate({
scrollTop: $("#cat-"+openCat).offset().top
}, 500);
}
return obj;
}
});
function getId(parent) {
if (parent.getLevel() == 0) {
@@ -170,6 +181,9 @@ function getRank(node, ignoreId = null) {
}
function applyMove(event) {
waitingTimeout = setTimeout(() => {
$('.waiting-message').addClass('visible');
}, 500);
id = event.move_info.moved_node.id;
moveParent = null;
moveRank = null;
@@ -186,7 +200,11 @@ function applyMove(event) {
}
moveRank = 1;
}
moveNode(id, moveRank, moveParent).then(() => event.move_info.do_move())
moveNode(id, moveRank, moveParent).then(() => {
event.move_info.do_move();
clearTimeout(waitingTimeout);
$('.waiting-message').removeClass('visible');
})
.catch((message) => console.log('An error has occured : ' + message ));
}
@@ -198,7 +216,6 @@ function moveNode(node, rank, parent) {
changeRank(node, rank).then(() => res()).catch(() => rej())
}
})
}
function changeParent(node, parent) {
@@ -249,3 +266,18 @@ function changeRank(node, rank) {
});
})
}
function makePrivateHierarchy (node) {
node.status = 'private';
node.children.forEach(node => {
makePrivateHierarchy(node);
});
}
function getPathNode(node) {
if (node.parent.getLevel() != 0) {
return getPathNode(node.parent) + ' / ' + node.name;
} else {
return node.name;
}
}
+57 -3
View File
@@ -6,9 +6,11 @@ var str_hide_sub = "{'Hide sub-albums'|@translate}";
var str_manage_sub_album = "{'Manage sub-albums'|@translate}";
var str_apply_order = "{'Apply an automatic order to sub-albums'|@translate}";
var str_edit = "{'Edit album'|@translate}";
var str_are_you_sure = "{'Album \'%s\' status will change to private'}"
var str_yes_change_parent = "{'Yes, change parent anyway'|@translate}"
var str_no_change_parent = "{"No, I have changed my mind"|@translate}"
var str_are_you_sure = "{'Album \'%s\' status and his sub-albums will change to private. Are you sure ?'|@translate}";
var str_yes_change_parent = "{'Yes change parent anyway'|@translate}"
var str_no_change_parent = "{'No, don\'t move this album here'|@translate}"
var str_root = "{'Root'|@translate}"
var openCat = {$open_cat};
{/footer_script}
{combine_script id='jquery.confirm' load='footer' require='jquery' path='themes/default/js/plugins/jquery-confirm.min.js'}
@@ -24,6 +26,58 @@ var str_no_change_parent = "{"No, I have changed my mind"|@translate}"
<h2>{'Move albums'|@translate}</h2>
</div>
<div class="waiting-message"> <i class='icon-spin6 animate-spin'> </i> Waiting for Piwigo response...</div>
<div class="cat-move-order-popin">
<div class="order-popin-container">
<a class="close-popin icon-cancel" onClick="$('.cat-move-order-popin').fadeOut()"> </a>
<div class="album-name icon-flow-tree"></div>
<form action="{$F_ACTION}" method="post">
<input type="hidden" name="id" value="-1">
<div class="choice-container">
<label>
{'Album name, A &rarr; Z'|@translate}
<input type="radio" value="name ASC" name="order" checked>
<span class="order-checkmark">
</label>
<label>
{'Album name, Z &rarr; A'|@translate}
<input type="radio" value="name DESC" name="order">
<span class="order-checkmark">
</label>
<label>
{'Date created, new &rarr; old'|@translate}
<input type="radio" value="date_creation DESC" name="order">
<span class="order-checkmark">
</label>
<label>
{'Date created, old &rarr; new'|@translate}
<input type="radio" value="date_creation ASC" name="order">
<span class="order-checkmark">
</label>
<label>
{'Date posted, new &rarr; old'|@translate}
<input type="radio" value="date_available DESC" name="order">
<span class="order-checkmark">
</label>
<label>
{'Date posted, old &rarr; new'|@translate}
<input type="radio" value="date_available ASC" name="order">
<span class="order-checkmark">
</label>
</div>
<input type="submit" name="simpleAutoOrder" value="{'Apply to direct sub-albums'|@translate}"/>
<input type="submit" name="recursiveAutoOrder" value="{'Apply to the whole hierarchy'|@translate}"/>
</form>
</div>
</div>
<div class="cat-move-header">
<div class="cat-move-info icon-help-circled"> {'Drag and drop to reorder albums'|@translate}</div>
<a class="order-root icon-flow-tree"> {'Apply an automatic order to root albums'|@translate} </a>
+145 -8
View File
@@ -446,6 +446,27 @@ TABLE.doubleSelect SELECT.categoryList {
text-decoration: none;
}
/* Waiting Message */
.waiting-message {
position: fixed;
right: 0px;
top: 0px;
margin: 10px;
z-index: 1000;
background-color: #FFBD4D;
color: white;
padding: 20px;
font-weight: bold;
border-radius: 34px;
transform: translateY(calc(-100% - 10px));
transition: 0.5s ease;
}
.waiting-message.visible {
transform: translateY(0%);
}
/* Statistic Page */
.stat-legend-container {
@@ -2728,6 +2749,7 @@ FORM#categoryOrdering p.albumActions a {
FORM#categoryOrdering p.albumActions a:hover {text-decoration: none;}
FORM#categoryOrdering p.albumActions .userSeparator {margin:0 5px;}
/* Move Album */
.cat-move-header {
display: flex;
margin: 0px 30px;
@@ -2740,11 +2762,22 @@ FORM#categoryOrdering p.albumActions .userSeparator {margin:0 5px;}
font-size: 16px;
}
.cat-move-header .cat-move-info {
position: relative;
}
.cat-move-header .cat-move-info::before {
color: grey;
font-size: 20px;
}
.cat-move-header .cat-move-info .spinner {
position: absolute;
left: calc(100% + 6px);
font-size: 20px;
display: none;
}
.tree {
margin: 20px;
}
@@ -2752,11 +2785,11 @@ FORM#categoryOrdering p.albumActions .userSeparator {margin:0 5px;}
.move-cat-container {
display: flex;
align-items: center;
box-shadow: 0px 0px 4px #acacac;
box-shadow: 0px 2px 3px #00000024;
background-color: #fafafa;
border-radius: 5px;
color: #777;
padding: 10px;
padding: 5px 10px;
padding-left: 20px;
}
@@ -2771,7 +2804,7 @@ FORM#categoryOrdering p.albumActions .userSeparator {margin:0 5px;}
display: inline-block;
border-radius: 50%;
font-size: 20px;
padding: 10px;
padding: 8px;
margin: 5px;
}
@@ -2781,7 +2814,7 @@ FORM#categoryOrdering p.albumActions .userSeparator {margin:0 5px;}
}
.move-cat-container .move-cat-title {
font-size: 20px;
font-size: 16px;
margin: 10px;
max-width: 200px;
overflow: hidden;
@@ -2819,15 +2852,17 @@ FORM#categoryOrdering p.albumActions .userSeparator {margin:0 5px;}
.move-cat-action {
display: flex;
align-items: center;
max-width: 70%;
max-width: 60%;
}
.move-cat-action a {
padding: 35px;
color: #777;
font-size: 12px;
padding: 30px 15px;
font-weight: bold;
cursor: pointer;
height: 100%;
border-left: 1px solid #00000012;
background-color: #00000003;
text-align: center;
}
.move-cat-action a:hover {
@@ -2836,6 +2871,108 @@ FORM#categoryOrdering p.albumActions .userSeparator {margin:0 5px;}
background-color: #FFA646;
}
.cat-move-order-popin {
position: fixed;
z-index: 100;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.7);
display: none;
}
.cat-move-order-popin .order-popin-container {
background-color: #f3f3f3;
border-radius: 10px;
display: block;
width: 30%;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -48%);
text-align: left;
padding: 30px;
}
.cat-move-order-popin .album-name {
font-size: 20px;
color: #3C3C3C;
}
.cat-move-order-popin .choice-container {
display: flex;
flex-direction: column;
padding: 15px 5px;
}
.cat-move-order-popin label {
position: relative;
padding: 5px;
padding-left: 25px;
}
.cat-move-order-popin .order-checkmark {
width:16px;
height: 16px;
border-radius: 100%;
background-color: #d5d5d5;
position: absolute;
top: 50%;
left: 0px;
transform: translate(0, -50%);
transition: ease 0.3s;
}
.cat-move-order-popin .order-checkmark::before {
content: "";
padding: 0px;
background-color: #ffa646;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 100%;
transition: ease 0.3s;
opacity: 0.15;
}
.cat-move-order-popin input:checked + .order-checkmark::before {
padding: 15px;
}
.cat-move-order-popin input:checked + .order-checkmark {
background-color: #ffa646;
}
.cat-move-order-popin input[type=radio] {
opacity: 0;
}
.cat-move-order-popin input[type=submit] {
width: 100%;
margin: 10px 0px;
}
.cat-move-order-popin input[name=recursive] {
background-color: #d9d9d9;
}
.cat-move-order-popin input[name=recursive]:hover {
background-color: #ffc17e;
}
.cat-move-order-popin .close-popin {
position: absolute;
right: -40px;
top: -40px;
font-size: 30px;
color: white;
}
.cat-move-order-popin .close-popin:hover { color: #ffa646;}
/*Overwrite JQtree CSS*/
.jqtree_common {
+6
View File
@@ -1029,6 +1029,12 @@ li.plupload_delete a:hover {background: url("images/cancelhover.svg")!important;
background-color:#f22;
}
/* Album Move */
.move-cat-container {
background-color: #333;
box-shadow: none;
}
/* Icon colors*/
.icon-red {