Files
Aberwyn/Aberwyn/wwwroot/js/budget-dragdrop.js
Elias Jansson 3759769ea0
All checks were successful
continuous-integration/drone/push Build is passing
Budget, admin och lite css
2025-05-21 00:13:47 +02:00

232 lines
7.8 KiB
JavaScript

// Drag & Drop directives for Budget Page
app.directive('draggableItem', function () {
return {
restrict: 'A',
scope: {
item: '=',
category: '=',
onItemDrop: '&'
},
link: function (scope, element) {
function isEditing() {
return scope.category && scope.category.editing;
}
function updateDraggableAttr() {
element[0].setAttribute('draggable', isEditing() ? 'true' : 'false');
element[0].classList.toggle('draggable-enabled', isEditing());
}
updateDraggableAttr();
scope.$watch('category.editing', updateDraggableAttr);
element[0].addEventListener('dragstart', function (e) {
if (!isEditing()) {
e.preventDefault();
return;
}
e.dataTransfer.setData('text/plain', JSON.stringify({
type: 'item',
fromCategoryId: scope.category.id,
itemId: scope.item.id
}));
const ghost = element[0].cloneNode(true);
ghost.style.position = 'absolute';
ghost.style.top = '-9999px';
ghost.style.left = '-9999px';
ghost.style.width = element[0].offsetWidth + 'px';
ghost.style.opacity = '0.7';
document.body.appendChild(ghost);
e.dataTransfer.setDragImage(ghost, 0, 0);
setTimeout(() => document.body.removeChild(ghost), 0);
document.querySelectorAll('.drop-zone').forEach(el => el.classList.add('active-drop-target'));
});
element[0].addEventListener('dragend', function () {
document.querySelectorAll('.drop-zone').forEach(el => el.classList.remove('active-drop-target'));
});
}
};
});
app.directive('dropPlaceholder', function () {
return {
restrict: 'A',
scope: {
category: '=',
index: '=',
onDropItem: '&'
},
link: function (scope, element) {
function isEditing() {
return scope.category && scope.category.editing;
}
element.addClass('drop-zone');
element.addClass('drop-placeholder');
const label = document.createElement('div');
label.innerText = 'DROP HERE';
label.style.textAlign = 'center';
label.style.fontSize = '12px';
label.style.color = '#166534';
label.style.display = 'none';
element[0].appendChild(label);
element[0].addEventListener('dragover', function (e) {
if (!isEditing()) return;
const rawData = e.dataTransfer.getData('text/plain');
if (!rawData.includes('"type":"item"')) return;
e.preventDefault();
element[0].classList.add('drag-over');
label.style.display = 'block';
});
element[0].addEventListener('dragleave', function () {
element[0].classList.remove('drag-over');
label.style.display = 'none';
});
element[0].addEventListener('drop', function (e) {
if (!isEditing()) return;
e.preventDefault();
element[0].classList.remove('drag-over');
label.style.display = 'none';
const dataText = e.dataTransfer.getData('text/plain');
try {
const data = JSON.parse(dataText);
if (data.type !== 'item') return;
scope.$apply(() => {
scope.onDropItem({
data: data,
targetCategory: scope.category,
targetIndex: scope.index
});
});
} catch (err) {
console.error("Error parsing drop data:", err);
}
});
}
};
});
app.directive('dropFallback', function () {
return {
restrict: 'A',
scope: {
category: '=',
onDropItem: '&'
},
link: function (scope, element) {
element.addClass('drop-zone');
const label = document.createElement('div');
label.innerText = 'DROP HERE';
label.style.textAlign = 'center';
label.style.fontSize = '12px';
label.style.color = '#166534';
label.style.display = 'none';
element[0].appendChild(label);
element[0].addEventListener('dragover', function (e) {
const data = e.dataTransfer.getData('text/plain');
if (!data.includes('"type":"item"')) return;
e.preventDefault();
element[0].classList.add('drag-over');
label.style.display = 'block';
});
element[0].addEventListener('dragleave', function () {
element[0].classList.remove('drag-over');
label.style.display = 'none';
});
element[0].addEventListener('drop', function (e) {
e.preventDefault();
element[0].classList.remove('drag-over');
label.style.display = 'none';
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
if (data.type !== 'item') return;
scope.$apply(() => {
scope.onDropItem({
data: data,
targetCategory: scope.category,
targetIndex: scope.category.items.length
});
});
});
}
};
});
app.directive('draggableCategory', function () {
return {
restrict: 'A',
scope: {
category: '=',
onCategoryDrop: '&'
},
link: function (scope, element) {
const header = element[0].querySelector('.card-header');
if (!header) return;
function updateDraggableAttr() {
header.setAttribute('draggable', scope.category.allowDrag ? 'true' : 'false');
}
scope.$watch('category.allowDrag', updateDraggableAttr);
updateDraggableAttr();
header.addEventListener('dragstart', function (e) {
if (!scope.category.allowDrag) {
e.preventDefault();
return;
}
e.dataTransfer.setData('text/plain', JSON.stringify({
type: 'category',
categoryId: scope.category.id
}));
});
element[0].addEventListener('dragover', function (e) {
e.preventDefault();
});
element[0].addEventListener('drop', function (e) {
e.preventDefault();
element[0].classList.remove('drag-over');
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
if (data.type !== 'category') return;
scope.$apply(() => {
scope.onCategoryDrop({
data: data,
targetCategory: scope.category
});
});
});
element[0].addEventListener('dragenter', function (e) {
e.preventDefault();
element[0].classList.add('drag-over');
});
element[0].addEventListener('dragleave', function (e) {
e.preventDefault();
element[0].classList.remove('drag-over');
});
}
};
});