142 lines
4.3 KiB
JavaScript
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
|
|
};
|
|
})();
|