472 lines
16 KiB
JavaScript
472 lines
16 KiB
JavaScript
let onPickedElement = function (element) {
|
|
};
|
|
|
|
jQuery(function ($) {
|
|
let lastClicked = null;
|
|
let selectionStart = null;
|
|
let selectionRectangle = null;
|
|
|
|
const startSelection = function (event) {
|
|
// mouse left button only
|
|
if (event.button !== 0) {
|
|
return;
|
|
}
|
|
selectionStart = {x: event.pageX, y: event.pageY};
|
|
|
|
selectionRectangle = $('<div class="selection-rectangle"></div>');
|
|
$('body').append(selectionRectangle);
|
|
|
|
$(document).on('mousemove', updateSelection);
|
|
$(document).on('mouseup', endSelection);
|
|
|
|
event.preventDefault();
|
|
};
|
|
|
|
const isMajorSelection = function () {
|
|
if (!selectionStart) return false;
|
|
const width = selectionRectangle.width();
|
|
const height = selectionRectangle.height()
|
|
return (width > 40 && height > 40)
|
|
};
|
|
|
|
const updateSelection = function (event) {
|
|
if ($('body').hasClass('dragging')) {
|
|
endSelection()
|
|
return;
|
|
}
|
|
|
|
if (!selectionStart) return;
|
|
|
|
const current = {x: event.pageX, y: event.pageY};
|
|
const top = Math.min(selectionStart.y, current.y);
|
|
const left = Math.min(selectionStart.x, current.x);
|
|
const width = Math.abs(selectionStart.x - current.x);
|
|
const height = Math.abs(selectionStart.y - current.y);
|
|
|
|
selectionRectangle.css({top, left, width, height});
|
|
|
|
$('.explr-dirview li > a.explr-link').each(function () {
|
|
const $link = $(this);
|
|
const linkOffset = $link.offset();
|
|
const linkWidth = $link.outerWidth();
|
|
const linkHeight = $link.outerHeight();
|
|
|
|
const isWithinSelection = (
|
|
linkOffset.left < left + width &&
|
|
linkOffset.left + linkWidth > left &&
|
|
linkOffset.top < top + height &&
|
|
linkOffset.top + linkHeight > top
|
|
);
|
|
|
|
if (isMajorSelection()) {
|
|
if (isWithinSelection) {
|
|
highlightExplrLink($link);
|
|
} else {
|
|
unhighlightExplrLink($link);
|
|
}
|
|
}
|
|
});
|
|
|
|
updateBodyClasses();
|
|
};
|
|
|
|
const endSelection = function (event) {
|
|
$(document).off('mousemove', updateSelection);
|
|
$(document).off('mouseup', endSelection);
|
|
|
|
if (selectionRectangle) {
|
|
selectionRectangle.remove();
|
|
setTimeout(function () {
|
|
selectionRectangle = null;
|
|
selectionStart = null;
|
|
}, 100);
|
|
}
|
|
|
|
updateBodyClasses();
|
|
};
|
|
|
|
const explrSidebarOpenFromFolder = function (folderId) {
|
|
const $leaf = $('.li-explr-folder-' + folderId);
|
|
let $parent = $leaf;
|
|
while ($parent.length > 0) {
|
|
const $toggler = $parent.find('.explr-toggler:eq(0)');
|
|
if ($toggler.hasClass('explr-plus')) {
|
|
$parent.find('.explr-toggler:eq(0)').trigger('click');
|
|
}
|
|
$parent = $parent.parents('.li-explr-folder:eq(0)');
|
|
}
|
|
};
|
|
|
|
const initExplr = function () {
|
|
$('.explr').each(function () {
|
|
$(this).explr({
|
|
classesPlus: 'fa fa-chevron-right',
|
|
classesMinus: 'fa fa-chevron-down',
|
|
onLoadFinish: function ($tree) {
|
|
$tree.removeClass('hidden');
|
|
}
|
|
});
|
|
|
|
// Open complete path in explorer sidebar
|
|
explrSidebarOpenFromFolder($(this).attr('data-working-folder-id'));
|
|
});
|
|
|
|
$('.draggable').each(function () {
|
|
$(this).multiDraggable({
|
|
revert: "invalid",
|
|
revertDuration: 10,
|
|
group: 'li.highlight-clicked',
|
|
startNative: function (event, ui) {
|
|
$('body').addClass('dragging');
|
|
},
|
|
stopNative: function () {
|
|
$('body').removeClass('dragging');
|
|
}
|
|
});
|
|
});
|
|
|
|
$('.droppable').each(function () {
|
|
$(this).droppable({
|
|
accept: ".draggable",
|
|
over: function (event, ui) {
|
|
$(this).addClass("highlight-drop");
|
|
},
|
|
out: function (event, ui) {
|
|
$(this).removeClass("highlight-drop");
|
|
},
|
|
drop: function (event, ui) {
|
|
$(this).removeClass("highlight-drop");
|
|
const $form = $('#folder-move-form');
|
|
const $moved = $('.ui-draggable-dragging');
|
|
const $target = $(this);
|
|
const folder_ids = [], entity_ids = [];
|
|
|
|
$moved.each(function () {
|
|
const $item = $(this);
|
|
const is_folder = $item.attr('data-folder') === '1';
|
|
const id = $item.attr('data-id');
|
|
|
|
if (is_folder) {
|
|
folder_ids.push(id);
|
|
} else {
|
|
entity_ids.push(id);
|
|
}
|
|
|
|
$item.position({
|
|
my: "center",
|
|
at: "center",
|
|
of: $target,
|
|
using: function (pos) {
|
|
$item.animate(pos, 50);
|
|
}
|
|
});
|
|
});
|
|
|
|
$form.find('[name=entity_ids]').val(entity_ids.join(','))
|
|
$form.find('[name=folder_ids]').val(folder_ids.join(','))
|
|
$form.find('[name=new_folder_id]').val($target.attr('data-id'))
|
|
$form.submit();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
const main = function () {
|
|
initExplr();
|
|
};
|
|
|
|
const updateBodyClasses = function () {
|
|
const $selectedLinks = $('a.explr-link.highlight-clicked');
|
|
const isMultiSelect = $selectedLinks.length > 1;
|
|
const isSingleSelect = $selectedLinks.length === 1;
|
|
const $link = $selectedLinks.last();
|
|
|
|
$('body')
|
|
.toggleClass('explr-selection', isSingleSelect)
|
|
.toggleClass('explr-selection-actionable', isSingleSelect && $link.hasClass('explr-item-actionable'))
|
|
.toggleClass('explr-selection-entity', isSingleSelect && $link.hasClass('explr-item-entity'))
|
|
.toggleClass('explr-selection-folder', isSingleSelect && $link.hasClass('explr-item-folder'))
|
|
.toggleClass('explr-multiselection', isMultiSelect)
|
|
.toggleClass('explr-multiselection-actionable', isMultiSelect && $selectedLinks.hasClass('explr-item-actionable'))
|
|
.toggleClass('explr-multiselection-entity', isMultiSelect && $selectedLinks.hasClass('explr-item-entity'))
|
|
.toggleClass('explr-multiselection-folder', isMultiSelect && $selectedLinks.hasClass('explr-item-folder'));
|
|
};
|
|
|
|
const highlightExplrLink = function ($link) {
|
|
$link.addClass('highlight-clicked');
|
|
$link.parent().addClass('highlight-clicked');
|
|
};
|
|
|
|
const unhighlightExplrLink = function ($link) {
|
|
$link.removeClass('highlight-clicked');
|
|
$link.parent().removeClass('highlight-clicked');
|
|
};
|
|
|
|
const selectEpxlrLink = function ($link) {
|
|
highlightExplrLink($link);
|
|
updateBodyClasses();
|
|
};
|
|
|
|
const clearSelection = function () {
|
|
unhighlightExplrLink($('a.explr-link'));
|
|
$('body').removeClass('explr-selection explr-selection-actionable explr-selection-entity explr-selection-folder explr-multiselection explr-multiselection-actionable explr-multiselection-entity explr-multiselection-folder');
|
|
};
|
|
|
|
const handleShiftClick = function ($link) {
|
|
const $links = $('li > a.explr-link');
|
|
const start = $links.index(lastClicked);
|
|
const end = $links.index($link);
|
|
const [from, to] = start < end ? [start, end] : [end, start];
|
|
$links.slice(from, to + 1).each(function () {
|
|
selectEpxlrLink($(this));
|
|
});
|
|
updateBodyClasses();
|
|
};
|
|
|
|
const handleCmdCtrlClick = function ($link) {
|
|
if ($link.hasClass('highlight-clicked')) {
|
|
$link.removeClass('highlight-clicked');
|
|
$link.parent().removeClass('highlight-clicked');
|
|
} else {
|
|
selectEpxlrLink($link);
|
|
}
|
|
updateBodyClasses();
|
|
};
|
|
|
|
const getExplrSelection = function () {
|
|
return $('.explr-dirview li.highlight-clicked');
|
|
};
|
|
|
|
const renameExplrItem = function ($item) {
|
|
$('.dirview .renaming').removeClass('renaming');
|
|
$item.addClass('renaming');
|
|
$item.find('input').focus().select();
|
|
};
|
|
|
|
const getSelectedElements = function () {
|
|
return $('.explr-item-selectable.highlight-clicked');
|
|
};
|
|
|
|
$(document).on('click', '.explr-item-edit', function () {
|
|
document.location.href = $(this).attr('data-entity-route').replace('!c!', getExplrSelection().attr('data-id'));
|
|
});
|
|
|
|
$(document).on('click', '.explr-item-rename', function () {
|
|
renameExplrItem(getExplrSelection());
|
|
});
|
|
|
|
$(document).on('click', '.explr-item-delete', function () {
|
|
const $item = getExplrSelection();
|
|
const is_folder = $item.attr('data-folder') === '1';
|
|
let route;
|
|
|
|
if (is_folder) {
|
|
route = $(this).attr('data-folder-route') + '&id=' + $item.attr('data-id');
|
|
} else {
|
|
route = $(this).attr('data-entity-route') + '&id=' + $item.attr('data-id');
|
|
}
|
|
|
|
if (confirm(l.js_common_are_you_sure)) {
|
|
document.location.href = route;
|
|
}
|
|
});
|
|
|
|
$(document).on('click', '.explr-items-delete', function () {
|
|
const $items = getExplrSelection();
|
|
const folder_ids = [], entity_ids = [];
|
|
|
|
$items.each(function () {
|
|
const is_folder = $(this).attr('data-folder') === '1';
|
|
const id = $(this).attr('data-id');
|
|
|
|
if (is_folder) {
|
|
folder_ids.push(id);
|
|
} else {
|
|
entity_ids.push(id);
|
|
}
|
|
});
|
|
|
|
if (confirm(l.js_common_are_you_sure)) {
|
|
document.location.href = $(this).attr('data-route')
|
|
+ '&folder_ids=' + folder_ids.join(',')
|
|
+ '&entity_ids=' + entity_ids.join(',')
|
|
;
|
|
}
|
|
});
|
|
|
|
$(document).keyup(function (e) {
|
|
const $selectedLink = getSelectedElements();
|
|
const $selectedLinkLast = getSelectedElements().last();
|
|
const $selectedLi = $selectedLink.parents('li:eq(0)');
|
|
const $selectedLiLast = $selectedLinkLast.parents('li:eq(0)');
|
|
|
|
if (e.key === "Escape") {
|
|
$('.dirview .new-folder').addClass('hidden');
|
|
$('.dirview .renaming').removeClass('renaming');
|
|
clearSelection();
|
|
}
|
|
|
|
if ($('.renaming input:focus').length > 0) {
|
|
return;
|
|
}
|
|
|
|
if (e.code === "Space") {
|
|
renameExplrItem($selectedLi);
|
|
} else if ($selectedLink.length) {
|
|
const $prevLi = $selectedLi.prev('li:visible');
|
|
const $nextLi = $selectedLiLast.next('li:visible');
|
|
const verticalNeighbors = getAboveBelowElement($selectedLi);
|
|
const clearIfNoMeta = function () {
|
|
if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
|
|
clearSelection()
|
|
}
|
|
};
|
|
|
|
if (e.key === "Enter") {
|
|
$selectedLink.trigger('dblclick');
|
|
} else if (e.key === "ArrowLeft" && $prevLi.length) {
|
|
clearIfNoMeta();
|
|
selectEpxlrLink($prevLi.find('.explr-link'));
|
|
} else if (e.key === "ArrowRight" && $nextLi.length) {
|
|
clearIfNoMeta();
|
|
selectEpxlrLink($nextLi.find('.explr-link'));
|
|
} else if (e.key === "ArrowUp" && verticalNeighbors.above) {
|
|
clearIfNoMeta();
|
|
selectEpxlrLink(verticalNeighbors.above.find('.explr-link'));
|
|
} else if (e.key === "ArrowDown" && verticalNeighbors.below) {
|
|
clearIfNoMeta();
|
|
selectEpxlrLink(verticalNeighbors.below.find('.explr-link'));
|
|
} else if (e.key === "Backspace") {
|
|
if ($('.explr-item-delete:visible').length) {
|
|
$('.explr-item-delete:visible').click();
|
|
}
|
|
if ($('.explr-items-delete:visible').length) {
|
|
$('.explr-items-delete:visible').click();
|
|
}
|
|
}
|
|
} else if (e.key.indexOf('Arrow') === 0) {
|
|
selectEpxlrLink($('.explr-dirview li:visible:eq(0)').find('.explr-link'));
|
|
}
|
|
});
|
|
|
|
// Explorer item selection
|
|
$(document).on('mousedown', '.selectable-zone', startSelection);
|
|
|
|
$(document).on('click', 'a.explr-link', function (event) {
|
|
event.preventDefault();
|
|
const $link = $(this);
|
|
|
|
if (event.shiftKey && lastClicked) {
|
|
handleShiftClick($link);
|
|
} else if (event.metaKey || event.ctrlKey) {
|
|
handleCmdCtrlClick($link);
|
|
} else {
|
|
clearSelection();
|
|
selectEpxlrLink($link);
|
|
}
|
|
lastClicked = $link;
|
|
});
|
|
|
|
$(document).on('click', 'a.explr-pick-element', function (event) {
|
|
event.preventDefault();
|
|
const callback = $(this).attr('data-callback');
|
|
|
|
if (callback) {
|
|
window[callback]($(this));
|
|
}
|
|
});
|
|
|
|
$(document).on('dblclick', 'a.explr-link', function (event) {
|
|
event.preventDefault();
|
|
$(this).off('click');
|
|
const href = $(this).attr('href');
|
|
const callback = $(this).attr('data-callback');
|
|
|
|
if (callback) {
|
|
window[callback]($(this));
|
|
} else if ($(this).attr('target') === '_blank') {
|
|
window.open(href);
|
|
} else {
|
|
window.location.href = href;
|
|
}
|
|
});
|
|
|
|
$(document).on('click', function (event) {
|
|
const $parentClickable = $(event.target).parents('a, button');
|
|
if ($parentClickable.length === 0 && !isMajorSelection()) {
|
|
clearSelection();
|
|
}
|
|
});
|
|
|
|
$(document).on('click', '.modal-explr-picker .explr-pick-folder', function () {
|
|
$(this).parents('li:eq(0)').find('.explr-toggler').click();
|
|
});
|
|
|
|
$(document).on('click', '.modal-explr-picker .explr-pick-element', function () {
|
|
onPickedElement(JSON.parse($(this).parents('li:eq(0)').attr('data-entity-json')));
|
|
hidePicker();
|
|
});
|
|
|
|
const getAboveBelowElement = function ($elem) {
|
|
const $liElements = $elem.parents('ul:eq(0)').find('li');
|
|
const positions = [];
|
|
|
|
// Get the Y positions of each element
|
|
$liElements.each(function () {
|
|
const $this = $(this);
|
|
positions.push({
|
|
element: $this,
|
|
y: $this.offset().top,
|
|
x: $this.offset().left
|
|
});
|
|
});
|
|
|
|
// Group elements by their Y position
|
|
const groupedByY = positions.reduce((acc, pos) => {
|
|
if (!acc[pos.y]) {
|
|
acc[pos.y] = [];
|
|
}
|
|
acc[pos.y].push(pos);
|
|
return acc;
|
|
}, {});
|
|
|
|
// Convert groupedByY to an array of arrays
|
|
const rows = Object.values(groupedByY);
|
|
|
|
let targetRowIndex = -1;
|
|
let targetColIndex = -1;
|
|
|
|
// Find the row and column index of the target element
|
|
rows.forEach((row, rowIndex) => {
|
|
row.forEach((pos, colIndex) => {
|
|
if (pos.element.is($elem)) {
|
|
targetRowIndex = rowIndex;
|
|
targetColIndex = colIndex;
|
|
}
|
|
});
|
|
});
|
|
|
|
const result = {
|
|
above: null,
|
|
below: null
|
|
};
|
|
|
|
if (targetRowIndex > 0) {
|
|
const aboveRow = rows[targetRowIndex - 1];
|
|
if (targetColIndex < aboveRow.length) {
|
|
result.above = aboveRow[targetColIndex].element;
|
|
}
|
|
}
|
|
|
|
if (targetRowIndex < rows.length - 1) {
|
|
const belowRow = rows[targetRowIndex + 1];
|
|
if (targetColIndex < belowRow.length) {
|
|
result.below = belowRow[targetColIndex].element;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
main();
|
|
}); |