From a9b13e1b8a2032c3fd6b70f7a69b38cbe99940b0 Mon Sep 17 00:00:00 2001 From: maria touilzak Date: Wed, 11 Dec 2024 14:58:35 +0100 Subject: [PATCH] feat:Add context menu to search results for better file navigation (#876) --- src/gui/src/UI/UIWindowSearch.js | 268 +++++++++++++++---------------- src/gui/src/keyboard.js | 10 ++ 2 files changed, 136 insertions(+), 142 deletions(-) diff --git a/src/gui/src/UI/UIWindowSearch.js b/src/gui/src/UI/UIWindowSearch.js index ab28278da3..e056f696d3 100644 --- a/src/gui/src/UI/UIWindowSearch.js +++ b/src/gui/src/UI/UIWindowSearch.js @@ -1,35 +1,24 @@ /** - * 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 . + * Gestion du double-clic et du clic droit + * - Double-clic ouvre les fichiers et les dossiers. + * - Clic droit affiche un menu contextuel avec "Open Dir" pour les dossiers et "Open File" pour les fichiers. + * - Le menu contextuel disparaît et le cache est effacé après l'exécution de l'action. */ -import UIWindow from './UIWindow.js' -import path from "../lib/path.js" -import UIAlert from './UIAlert.js' -import launch_app from '../helpers/launch_app.js' -import item_icon from '../helpers/item_icon.js' +import UIWindow from './UIWindow.js'; +import path from "../lib/path.js"; +import UIAlert from './UIAlert.js'; +import launch_app from '../helpers/launch_app.js'; +import item_icon from '../helpers/item_icon.js'; +import UIContextMenu from './UIContextMenu.js'; -async function UIWindowSearch(options){ +async function UIWindowSearch(options) { let h = ''; - h += `
`; - h += ``; - h += `
`; - h += `
`; + h += `
`; + h += ``; + h += `
`; + h += `
`; const el_window = await UIWindow({ icon: null, @@ -49,8 +38,6 @@ async function UIWindowSearch(options){ allow_native_ctxmenu: true, allow_user_select: true, window_class: 'window-search', - onAppend: function(el_window){ - }, width: 500, dominant: true, window_css: { @@ -67,12 +54,12 @@ async function UIWindowSearch(options){ 'overflow': 'hidden', 'min-height': '65px', 'padding-bottom': '10px', - } + }, }); $(el_window).find('.search-input').focus(); - // Debounce function to limit rate of API calls + // Fonction debounce pour limiter les appels API function debounce(func, wait) { let timeout; return function (...args) { @@ -84,176 +71,173 @@ async function UIWindowSearch(options){ }; } - // State for managing loading indicator - let isSearching = false; - - // Debounced search function - const performSearch = debounce(async function(searchInput, resultsContainer) { - // Don't search if input is empty + const performSearch = debounce(async function (searchInput, resultsContainer) { if (searchInput.val() === '') { resultsContainer.html(''); resultsContainer.hide(); return; } - // Set loading state - if (!isSearching) { - isSearching = true; - } - try { - // Perform the search let results = await fetch(window.api_origin + '/search', { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${puter.authToken}` + 'Authorization': `Bearer ${puter.authToken}`, }, - body: JSON.stringify({ text: searchInput.val() }) + body: JSON.stringify({ text: searchInput.val() }), }); results = await results.json(); - // Hide results if there are none - if(results.length === 0) + if (results.length === 0) { resultsContainer.hide(); - else - resultsContainer.show(); + return; + } - // Build results HTML + resultsContainer.show(); let h = ''; - for(let i = 0; i < results.length; i++){ - const result = results[i]; + for (let result of results) { h += `
`; - // icon h += ``; h += html_encode(result.name); h += `
`; } + resultsContainer.html(h); } catch (error) { resultsContainer.html('
Search failed. Please try again.
'); console.error('Search error:', error); - } finally { - isSearching = false; } - }, 300); // Wait 300ms after last keystroke before searching + }, 300); - // Event binding - $(el_window).find('.search-input').on('input', function(e) { + $(el_window).find('.search-input').on('input', function () { const searchInput = $(this); const resultsContainer = $(el_window).find('.search-results'); performSearch(searchInput, resultsContainer); }); -} -$(document).on('click', '.search-result', async function(e){ + // Handle clicks on search results + +$(document).off('click', '.search-result').on('click', '.search-result', async function(e) { const fspath = $(this).data('path'); const fsuid = $(this).data('uid'); const is_dir = $(this).attr('data-is_dir') === 'true' || $(this).data('is_dir') === '1'; - let open_item_meta; - - if(is_dir){ - UIWindow({ - path: fspath, - title: path.basename(fspath), - icon: await item_icon({is_dir: true, path: fspath}), - uid: fsuid, - is_dir: is_dir, - app: 'explorer', - // top: options.maximized ? 0 : undefined, - // left: options.maximized ? 0 : undefined, - // height: options.maximized ? `calc(100% - ${window.taskbar_height + window.toolbar_height + 1}px)` : undefined, - // width: options.maximized ? `100%` : undefined, - }); - // close search window - $(this).closest('.window').close(); + if (is_dir) { + try { + UIWindow({ + path: fspath, + title: path.basename(fspath), + icon: await item_icon({ is_dir: true, path: fspath }), + uid: fsuid, + is_dir: true, + app: 'explorer', + }); - return; + // Close the search window + $(this).closest('.window').close(); + } catch (error) { + console.error('Error opening directory:', error); + } + } else { + openFile($(this)); } +}); + + // Handle right-click with context menu +$(document).off('contextmenu', '.search-result').on('contextmenu', '.search-result', async function (e) { + e.preventDefault(); // Prevents the default context menu + + const item = $(this); + const isDir = item.attr('data-is_dir') === 'true' || item.data('is_dir') === '1'; + $('.context-menu').remove(); + // Create the context menu with specific options + UIContextMenu({ + parent_element: $(this), + event: e, + items: isDir + ? [ + { + html: "Open Containing Folder", + onClick: async function () { + const dirPath = item.data('path'); + const dirUid = item.data('uid'); + + // Opens the directory + try { + UIWindow({ + path: dirPath, + title: path.basename(dirPath), + icon: await item_icon({ is_dir: true, path: dirPath }), + uid: dirUid, + is_dir: true, + app: 'explorer', + }); + } catch (error) { + console.error('Error opening directory:', error); + } + clearCache(); + }, + }, + ] + : [ + { + html: "Open File", + onClick: async function () { + openFile(item); // Calls the function to open the file + + clearCache(); + }, + }, + ], + }); +}); - // get all info needed to open an item - try{ +// Function to open a file +async function openFile(item) { + const filePath = item.data('path'); + const fileUid = item.data('uid'); + let open_item_meta; + + try { open_item_meta = await $.ajax({ url: window.api_origin + "/open_item", type: 'POST', contentType: "application/json", data: JSON.stringify({ - uid: fsuid ?? undefined, - path: fspath ?? undefined, + uid: fileUid ?? undefined, + path: filePath ?? undefined, }), headers: { - "Authorization": "Bearer "+window.auth_token - }, - statusCode: { - 401: function () { - window.logout(); - }, + "Authorization": "Bearer " + window.auth_token, }, }); - }catch(err){ - // Ignored - } - - // get a list of suggested apps for this file type. - let suggested_apps = open_item_meta?.suggested_apps ?? await window.suggest_apps_for_fsentry({uid: fsuid, path: fspath}); - - //--------------------------------------------- - // No suitable apps, ask if user would like to - // download - //--------------------------------------------- - if(suggested_apps.length === 0){ - //--------------------------------------------- - // If .zip file, unzip it - //--------------------------------------------- - if(path.extname(fspath) === '.zip'){ - window.unzipItem(fspath); - return; - } - const alert_resp = await UIAlert( - 'Found no suitable apps to open this file with. Would you like to download it instead?', - [ - { - label: i18n('download_file'), - value: 'download_file', - type: 'primary', - }, - { - label: i18n('cancel') - } - ]) - if(alert_resp === 'download_file'){ - window.trigger_download([fspath]); + const suggested_apps = open_item_meta?.suggested_apps ?? []; + if (suggested_apps.length > 0) { + launch_app({ + name: suggested_apps[0].name, + token: open_item_meta.token, + file_path: filePath, + app_obj: suggested_apps[0], + window_title: path.basename(filePath), + file_uid: fileUid, + }); + } else { + console.log("No suitable app to open the file."); } - return; - } - //--------------------------------------------- - // First suggested app is default app to open this item - //--------------------------------------------- - else{ - launch_app({ - name: suggested_apps[0].name, - token: open_item_meta.token, - file_path: fspath, - app_obj: suggested_apps[0], - window_title: path.basename(fspath), - file_uid: fsuid, - // maximized: options.maximized, - file_signature: open_item_meta.signature, - }); - } - + } catch (error) { + console.error('Error opening file:', error); + } +} - // close - $(this).closest('.window').close(); -}) +} -export default UIWindowSearch \ No newline at end of file +export default UIWindowSearch; diff --git a/src/gui/src/keyboard.js b/src/gui/src/keyboard.js index 9f3fedf27c..44f3d7ea6a 100644 --- a/src/gui/src/keyboard.js +++ b/src/gui/src/keyboard.js @@ -327,6 +327,7 @@ $(document).bind('keydown', async function(e){ $(selected_item).removeClass('search-result-active'); $(new_selected_item).addClass('search-result-active'); new_selected_item.scrollIntoView(false); + } } //----------------------------------------------------------------------- @@ -760,4 +761,13 @@ $(document).bind("keyup keydown", async function(e){ window.undo_last_action(); return false; } + // When hovering over a search result element + $(document).on('mouseenter', '.search-result', function () { + $(this).addClass('search-result-active'); // Adds the 'hover' class on hover + }); + + // When leaving a search result element + $(document).on('mouseleave', '.search-result', function () { + $(this).removeClass('search-result-active'); // Removes the 'hover' class when leaving + }); }); \ No newline at end of file