/** * Copyright (C) 2024 Puter Technologies Inc. * * This file is part of Puter. * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ function UIContextMenu(options){ $('.window-active .window-app-iframe').css('pointer-events', 'none'); const menu_id = global_element_id++; let h = ''; h += `
`; for(let i=0; i < options.items.length; i++){ // item if(!options.items[i].is_divider && options.items[i] !== '-'){ // single item if(options.items[i].items === undefined){ h += `
  • `; // icon h += `${options.items[i].icon ?? ''}`; h += `${options.items[i].icon_active ?? (options.items[i].icon ?? '')}`; // label h += `${options.items[i].html}`; h += `${options.items[i].html_active ?? options.items[i].html}`; h += `
  • `; } // submenu else{ h += `
  • `; // icon h += `${options.items[i].icon ?? ''}`; h += `${options.items[i].icon_active ?? (options.items[i].icon ?? '')}`; // label h += `${html_encode(options.items[i].html)}`; // arrow h += ``; h += `
  • `; } } // divider else if(options.items[i].is_divider || options.items[i] === '-') h += `

  • `; } h += `
    ` $('body').append(h) const contextMenu = document.getElementById(`context-menu-${menu_id}`); const menu_width = $(contextMenu).width(); const menu_height = $(contextMenu).outerHeight(); let start_x, start_y; //-------------------------------- // Auto position //-------------------------------- if(!options.position){ if(isMobile.phone || isMobile.tablet){ start_x = window.last_touch_x; start_y = window.last_touch_y; }else{ start_x = window.mouseX; start_y = window.mouseY; } } //-------------------------------- // custom position //-------------------------------- else{ start_x = options.position.left; start_y = options.position.top; } // X position let x_pos; if( start_x + menu_width > window.innerWidth){ x_pos = start_x - menu_width; // if this is a child menu, the width of parent must be also considered if(options.parent_id){ x_pos -= $(`.context-menu[data-element-id="${options.parent_id}"]`).width() + 30; } }else x_pos = start_x // Y position let y_pos; // is the menu going to go out of the window from the bottom? if( (start_y + menu_height) > (window.innerHeight - taskbar_height - 10)) y_pos = window.innerHeight - menu_height - taskbar_height - 10; else y_pos = start_y; // Show ContextMenu $(contextMenu).delay(100).show(0) // In the right position (the mouse) .css({ top: y_pos + "px", left: x_pos + "px" }); // mark other context menus as inactive $('.context-menu').not(contextMenu).removeClass('context-menu-active'); // An item is clicked $(`#context-menu-${menu_id} > li:not(.context-menu-item-disabled)`).on('click', function (e) { // onClick if(options.items[$(this).attr("data-action")].onClick && typeof options.items[$(this).attr("data-action")].onClick === 'function'){ let event = e; event.value = options.items[$(this).attr("data-action")]['val'] ?? undefined; options.items[$(this).attr("data-action")].onClick(event); } // close menu and, if exists, its parent if(!$(this).hasClass('context-menu-item-submenu')){ $(`#context-menu-${menu_id}, .context-menu[data-element-id="${$(this).closest('.context-menu').attr('data-parent-id')}"]`).fadeOut(200, function(){ $(contextMenu).remove(); }); } return false; }); // when mouse is over an item $(contextMenu).find('.context-menu-item').on('mouseover', function (e) { // mark other items as inactive $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); // mark this item as active $(this).addClass('context-menu-item-active'); // close any submenu that doesn't belong to this item $(`.context-menu[data-parent-id="${menu_id}"]`).remove(); // mark this context menu as active $(contextMenu).addClass('context-menu-active'); }) // open submenu if applicable $(`#context-menu-${menu_id} > li.context-menu-item-submenu`).on('mouseover', function (e) { // open submenu only if it's not already open if($(`.context-menu[data-id="${menu_id}-${$(this).attr('data-action')}"]`).length === 0){ let item_rect_box = this.getBoundingClientRect(); // close other submenus $(`.context-menu[parent-element-id="${menu_id}"]`).remove(); // open the new submenu UIContextMenu({ items: options.items[parseInt($(this).attr('data-action'))].items, parent_id: menu_id, is_submenu: true, id: menu_id + '-' + $(this).attr('data-action'), position:{ top: item_rect_box.top - 5, left: x_pos + item_rect_box.width + 15, } }) } return false; }); // useful in cases such as where a menue item is over a window, this prevents from the mousedown event // reaching the window underneath $(`#context-menu-${menu_id} > li:not(.context-menu-item-disabled)`).on('mousedown', function (e) { e.preventDefault(); e.stopPropagation(); return false; }) //disable parent scroll if(options.parent_element){ $(options.parent_element).css('overflow', 'hidden'); $(options.parent_element).parent().addClass('children-have-open-contextmenu'); $(options.parent_element).addClass('has-open-contextmenu'); } $(contextMenu).on("remove", function () { // when removing, make parent scrollable again if(options.parent_element){ $(options.parent_element).parent().removeClass('children-have-open-contextmenu'); $(options.parent_element).css('overflow', 'scroll'); $(options.parent_element).removeClass('has-open-contextmenu'); if($(options.parent_element).hasClass('taskbar-item')){ make_taskbar_sortable() } } }) $(contextMenu).on("contextmenu", function (e) { e.preventDefault(); e.stopPropagation(); return false; }) } window.select_ctxmenu_item = function ($ctxmenu_item){ // remove active class from other items $($ctxmenu_item).siblings('.context-menu-item').removeClass('context-menu-item-active'); // add active class to the selected item $($ctxmenu_item).addClass('context-menu-item-active'); } export default UIContextMenu;