Files
Aniworld/src/server/web/static/js/index/context-menu.js
2026-08-16 19:53:38 +02:00

142 lines
4.3 KiB
JavaScript

/**
* AniWorld - Context Menu Component
*
* Right-click context menu for anime series cards.
* Provides quick access to per-anime settings.
*
* Dependencies: ui-utils.js
*/
var AniWorld = window.AniWorld || {};
AniWorld.ContextMenu = (function() {
'use strict';
let menuElement = null;
let currentSeriesKey = null;
/**
* Initialize the context menu system.
* Attaches global dismissal listeners.
*/
function init() {
// Dismiss on click outside
document.addEventListener('click', function(e) {
if (menuElement && !menuElement.contains(e.target)) {
hide();
}
});
// Dismiss on Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
hide();
}
});
// Dismiss on scroll or resize
window.addEventListener('scroll', hide, true);
window.addEventListener('resize', hide);
// Attach context menu via event delegation on the series grid
const grid = document.getElementById('series-grid');
if (grid) {
grid.addEventListener('contextmenu', function(e) {
const card = e.target.closest('.series-card');
if (card) {
e.preventDefault();
const key = card.getAttribute('data-key');
if (key) {
show(e, key);
}
}
});
}
}
/**
* Show context menu at cursor position.
* @param {MouseEvent} event - The contextmenu event
* @param {string} seriesKey - The series key to operate on
*/
function show(event, seriesKey) {
hide(); // Remove any existing menu first
currentSeriesKey = seriesKey;
menuElement = document.createElement('div');
menuElement.className = 'context-menu';
menuElement.innerHTML = `
<div class="context-menu-item" data-action="anime-settings">
<i class="fa-solid fa-gear"></i>
<span>Anime Settings</span>
</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item danger" data-action="delete-anime">
<i class="fa-solid fa-trash"></i>
<span>Delete Anime</span>
</div>
`;
document.body.appendChild(menuElement);
// Position within viewport bounds
const x = event.clientX;
const y = event.clientY;
const menuRect = menuElement.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let posX = x;
let posY = y;
if (x + menuRect.width > viewportWidth) {
posX = viewportWidth - menuRect.width - 8;
}
if (y + menuRect.height > viewportHeight) {
posY = viewportHeight - menuRect.height - 8;
}
menuElement.style.left = posX + 'px';
menuElement.style.top = posY + 'px';
// Attach action handlers
// Anime Settings - opens the per-anime settings page
menuElement.querySelector('[data-action="anime-settings"]').addEventListener('click', function() {
// Capture the key BEFORE hide() clears it
const key = currentSeriesKey;
hide();
// Navigate to anime settings page with this series selected
window.location.href = '/anime/settings?key=' + encodeURIComponent(key);
});
// Delete Anime - opens the confirmation modal
menuElement.querySelector('[data-action="delete-anime"]').addEventListener('click', function() {
const key = currentSeriesKey;
hide();
if (AniWorld.DeleteModal && AniWorld.DeleteModal.show) {
AniWorld.DeleteModal.show(key);
} else {
console.error('[ContextMenu] DeleteModal not found on AniWorld');
}
});
}
/**
* Hide and remove the context menu from DOM.
*/
function hide() {
if (menuElement) {
menuElement.remove();
menuElement = null;
currentSeriesKey = null;
}
}
return {
init: init,
show: show,
hide: hide
};
})();