obscreen/data/www/js/explorer.js
2024-07-17 18:07:00 +02:00

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();
});