diff --git a/src/gui/src/UI/UIItem.js b/src/gui/src/UI/UIItem.js index 20a34d2fa2..1bdd39cd15 100644 --- a/src/gui/src/UI/UIItem.js +++ b/src/gui/src/UI/UIItem.js @@ -513,6 +513,94 @@ function UIItem(options){ $('.item-container').droppable( 'enable' ) } }); + // -------------------------------------------------------- + // Droppable/sidebar + // -------------------------------------------------------- + function initializeSidebarDroppable() { + $('.window-sidebar').droppable({ + accept: '.item', + tolerance: 'pointer', + hoverClass: 'window-sidebar-drop-hover', + drop: function(event, ui) { + const $draggedItem = $(ui.draggable); + + if (!$draggedItem.attr('data-path')) { + return false; + } + + let favorites = []; + try { + + favorites = JSON.parse(window.sidebar_items || "[]"); + } catch (e) { + console.error('Error parsing sidebar_items:', e); + favorites = []; + } + + let path = $draggedItem.attr('data-path'); + if (path) { + path = path.replace(/\\/g, '/'); + if (path.charAt(0) !== '/') { + path = '/' + path; + } + } + + // Create the favorite's object + let item = { + name: $draggedItem.attr('data-name'), + path: path, + label: $draggedItem.attr('data-name'), + type: $draggedItem.attr('data-is_dir') === 'true' || $draggedItem.attr('data-is_dir') === '1' ? 'folder' : 'file', + file_uid: $draggedItem.attr('data-uid') + }; + + // Check if already in favorites + let is_favorite = favorites.some(fav => fav.path === item.path); + + if (!is_favorite) { + + favorites.push(item); + window.sidebar_items = JSON.stringify(favorites); + localStorage.setItem("sidebar_items", window.sidebar_items); + + if (typeof rebuild_all_sidebars === 'function') { + rebuild_all_sidebars(); + } else { + + setTimeout(() => { + if (typeof load_dir === 'function') { + load_dir(window.current_path); + } + }, 200); + } + } + + $draggedItem.removeClass('item-selected'); + + return false; + } + }); + + // CSS for the hover effect + const style = document.createElement('style'); + style.textContent = ` + .window-sidebar-drop-hover { + border: 2px solid #2196F3; + border-radius: 4px; + } + `; + document.head.appendChild(style); + } + + $(document).ready(function() { + setTimeout(initializeSidebarDroppable, 1000); + }); + + $(document).on('window-opened window-focused', function() { + setTimeout(initializeSidebarDroppable, 300); + }); + + // -------------------------------------------------------- // Double Click/Single Tap on Item @@ -875,6 +963,60 @@ function UIItem(options){ } }); } + + + // ------------------------------------------- + // Add all to favorites + // ------------------------------------------- + menu_items.push({ + html: i18n('Add all to Favorites'), + onClick: function(){ + // Get current favorites + + let favorites = JSON.parse(window.sidebar_items || "[]"); + let addedCount = 0; + + //loop through selected items + $selected_items.each(function() { + // Skip trash items + if ($(this).attr('data-trashed') === '1' || $(this).attr('data-trash') === '1') { + return; + } + + // Get path and ensure consistency + let path = $(this).attr('data-path'); + if (path) { + path = path.replace(/\\/g, '/'); + if (path.charAt(0) !== '/') { + path = '/' + path; + } + } else { + return; // Skip items without a path + } + + let item = { + name: $(this).attr('data-name'), + path: path, + label: $(this).attr('data-name'), + type: $(this).attr('data-type') || ($(this).attr('data-is_dir') === '1' ? 'folder' : 'file'), + file_uid: $(this).attr('data-uid') + }; + + // Only add if it's not already in favorites + if (!favorites.some(fav => fav.path === item.path)) { + favorites.push(item); + addedCount++; + } + }); + + // Save back to window.sidebar_items + window.sidebar_items = JSON.stringify(favorites); + localStorage.setItem("sidebar_items", JSON.stringify(favorites)); + rebuild_all_sidebars(); + + } + }); + // ------------------------------------------- // - // ------------------------------------------- @@ -924,7 +1066,9 @@ function UIItem(options){ if(!are_trashed && window.feature_flags.create_shortcut){ menu_items.push({ html: i18n('create_shortcut'), + html: is_shared_with_me ? i18n('create_desktop_shortcut_s') : i18n('create_shortcut_s'), + onClick: async function(){ $selected_items.each(function() { let base_dir = path.dirname($(this).attr('data-path')); @@ -932,7 +1076,9 @@ function UIItem(options){ if($(this).attr('data-path') && $(this).closest('.item-container').attr('data-path') === window.desktop_path){ base_dir = window.desktop_path; } + if ( is_shared_with_me ) base_dir = window.desktop_path; + // create shortcut window.create_shortcut( path.basename($(this).attr('data-path')), @@ -1385,6 +1531,54 @@ function UIItem(options){ ); } }); + + + // ------------------------------------------- + // Add or Remove from Favorites + // ------------------------------------------- + + if ($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_trash) { + + let favorites = JSON.parse(window.sidebar_items || "[]"); + + let path = $(el_item).attr('data-path'); + if (path) { + path = path.replace(/\\/g, '/'); + if (path.charAt(0) !== '/') { + path = '/' + path; + } + } + + let item = { + name: $(el_item).attr('data-name'), + path: path, + label: $(el_item).attr('data-name'), + type: $(el_item).attr('data-type'), // Add file type + file_uid: $(el_item).attr('data-uid') + }; + + let is_favorite = favorites.some(fav => fav.path === item.path); + + menu_items.push({ + html: is_favorite ? i18n('Remove from favorites') : i18n('Add to favorites'), + onClick: function() { + if (is_favorite) { + + favorites = favorites.filter(fav => fav.path !== item.path); + // alert("Removed from favorites: " + item.path); + } else { + + favorites.push(item); + // alert("Added to favorites: " + item.path); + } + + window.sidebar_items = JSON.stringify(favorites); + localStorage.setItem("sidebar_items", JSON.stringify(favorites)); + rebuild_all_sidebars(); + } + }); + } + } // Create ContextMenu @@ -1418,6 +1612,72 @@ function UIItem(options){ } } +//Rebuilds all sidebar favorites lists in all windows +function rebuild_all_sidebars() { + $('.window-sidebar').each(function() { + + // Generate new sidebar HTML + let h = ''; + h += `

${i18n('favorites')}

`; + + // Parse all items + let items = JSON.parse(window.sidebar_items); + for(let item of items) { + let icon; + var filename = item.name; + + if(!window.sidebar_items){ + h += `
${i18n('home')}
`; + h += `
${i18n('documents')}
`; + h += `
${i18n('public')}
`; + h += `
${i18n('pictures')}
`; + h += `
${i18n('desktop')}
`; + h += `
${i18n('videos')}
`; + }else{ + if(item.path === window.home_path) + icon = window.icons['sidebar-folder-home.svg']; + else if(item.path === window.docs_path) + icon = window.icons['sidebar-folder-documents.svg']; + else if(item.path === window.public_path) + icon = window.icons['sidebar-folder-public.svg']; + else if(item.path === window.pictures_path) + icon = window.icons['sidebar-folder-pictures.svg']; + else if(item.path === window.desktop_path) + icon = window.icons['sidebar-folder-desktop.svg']; + else if(item.path === window.videos_path) + icon = window.icons['sidebar-folder-videos.svg']; + else if (item.type === 'folder') { + icon = window.icons['sidebar-folder.svg']; + } else if(filename && filename.includes('.')) { + // Get the extension type + var extension = filename.split('.').pop().toLowerCase(); + if(extension == 'txt') { + iconName = 'file.svg'; + } else { + // Create the SVG icon name string + var iconName = `file-${extension}.svg`; + } + icon = window.icons[iconName]; + } else { + //default folder icon + icon = window.icons['sidebar-folder.svg']; + } + + // Get the current window's path + const current_window = $(this).closest('.ui-window'); + const current_path = current_window.data('path') || ''; + + h += `
${html_encode(item.name)}
`; + } + + // Replace the sidebar content + $(this).html(h); + } + + }); +} + + // Create item-name-shadow // This element has the exact styling as item name editor and allows us // to measure the width and height of the item name editor and automatically diff --git a/src/gui/src/UI/UIWindow.js b/src/gui/src/UI/UIWindow.js index 3119e4eefd..04e0af92f2 100644 --- a/src/gui/src/UI/UIWindow.js +++ b/src/gui/src/UI/UIWindow.js @@ -31,6 +31,7 @@ import UIWindowEmailConfirmationRequired from './UIWindowEmailConfirmationRequir import launch_app from "../helpers/launch_app.js" import UIWindowShare from './UIWindowShare.js'; import item_icon from '../helpers/item_icon.js'; +import open_item from "../helpers/open_item.js" const el_body = document.getElementsByTagName('body')[0]; @@ -261,6 +262,53 @@ async function UIWindow(options) { h += ``; } + function initialize_sidebar() { + // Only set the default folders if sidebar_items is missing or empty + if (!window.sidebar_items ) { + // Default system folders + const system_directories = [ + { + path: window.home_path, + name: i18n('home'), + label: i18n('home'), + type: 'folder' + }, + { + path: window.docs_path, + name: i18n('documents'), + label: i18n('documents'), + type: 'folder' + }, + { + path: window.public_path, + name: i18n('public'), + label: i18n('public'), + type: 'folder' + }, + { + path: window.pictures_path, + name: i18n('pictures'), + label: i18n('pictures'), + type: 'folder' + }, + { + path: window.desktop_path, + name: i18n('desktop'), + label: i18n('desktop'), + type: 'folder' + }, + { + path: window.videos_path, + name: i18n('videos'), + label: i18n('videos'), + type: 'folder' + } + ]; + + // Store it as JSON string so downstream code can parse it + window.sidebar_items = JSON.stringify(system_directories); + } + } // Sidebar if(options.is_dir && !isMobile.phone){ h += `
`; // favorites h += `

${i18n('favorites')}

`; - // default items if sidebar_items is not set - if(!window.sidebar_items){ - h += `
${i18n('home')}
`; - h += `
${i18n('documents')}
`; - h += `
${i18n('public')}
`; - h += `
${i18n('pictures')}
`; - h += `
${i18n('desktop')}
`; - h += `
${i18n('videos')}
`; - }else{ - let items = JSON.parse(window.sidebar_items); - for(let item of items){ - let icon; - if(item.path === window.home_path) - icon = window.icons['sidebar-folder-home.svg']; - else if(item.path === window.docs_path) - icon = window.icons['sidebar-folder-documents.svg']; - else if(item.path === window.public_path) - icon = window.icons['sidebar-folder-public.svg']; - else if(item.path === window.pictures_path) - icon = window.icons['sidebar-folder-pictures.svg']; - else if(item.path === window.desktop_path) - icon = window.icons['sidebar-folder-desktop.svg']; - else if(item.path === window.videos_path) - icon = window.icons['sidebar-folder-videos.svg']; - else - icon = window.icons['sidebar-folder.svg']; - h += `
${html_encode(item.name)}
`; + + initialize_sidebar(); + + //add correct sidebar icons according to item type + let items = JSON.parse(window.sidebar_items); + for(let item of items){ + let icon; + var filename = item.name; + + if(item.path === window.home_path) + icon = window.icons['sidebar-folder-home.svg']; + else if(item.path === window.docs_path) + icon = window.icons['sidebar-folder-documents.svg']; + else if(item.path === window.public_path) + icon = window.icons['sidebar-folder-public.svg']; + else if(item.path === window.pictures_path) + icon = window.icons['sidebar-folder-pictures.svg']; + else if(item.path === window.desktop_path) + icon = window.icons['sidebar-folder-desktop.svg']; + else if(item.path === window.videos_path) + icon = window.icons['sidebar-folder-videos.svg']; + else if (item.type === 'folder') { + icon = window.icons['sidebar-folder.svg']; + } else if(filename && filename.includes('.') ){ + // Get the extension type + var extension = filename.split('.').pop().toLowerCase(); + if(extension == 'txt') { + iconName = 'file.svg'; + } else { + // Create the SVG icon name string + var iconName = `file-${extension}.svg`; + } + icon = window.icons[iconName]; + }else { + //default folder icon + icon = window.icons['sidebar-folder.svg']; } + h += `
${html_encode(item.name)}
`; } h += `
`; } @@ -2504,6 +2562,78 @@ function delete_window_element (el_window){ window.window_counter = 0; } +//open file from favorites + +$(document).on('click', '.window-sidebar-item', function(e) { + const $el = $(this); + const el_window = $el.closest('.window'); + const parent_win_id = el_window.attr('data-id'); + const item_path = $el.attr('data-path'); + + // Get file/folder metadata + puter.fs.stat(item_path, async function(fsentry) { + if (fsentry.is_dir) { + // === Handle folders === + if (e.metaKey || e.ctrlKey) { + // Open folder in a new window (Ctrl/Cmd + click) + UIWindow({ + path: item_path, + title: path.basename(item_path), + icon: await item_icon({ is_dir: true, path: item_path }), + is_dir: true, + app: 'explorer', + }); + } else if (item_path !== el_window.attr('data-path')) { + // Navigate to folder in the current window + window.window_nav_history[parent_win_id] = window.window_nav_history[parent_win_id].slice(0, window.window_nav_history_current_position[parent_win_id] + 1); + window.window_nav_history[parent_win_id].push(item_path); + window.window_nav_history_current_position[parent_win_id]++; + window.update_window_path(el_window, item_path); + } + } else { + // === Handle file === + const uid = fsentry.id; + const name = fsentry.name; + const associated_app_name = fsentry.associated_app?.name || ''; + + const fakeItem = createFakeItem({ + path: item_path, + is_dir: false, + uid, + name, + associated_app_name + }); + + if (e.ctrlKey || e.metaKey) { + open_item({ item: fakeItem, new_window: true }); + } else { + open_item({ item: fakeItem }); + } + } + }); + // Prevent default link behavior + return false; +}); + + + +// Create a fake DOM element representing a file or folder, +// so it can be used as if it was a real file system item + +function createFakeItem({ path, is_dir, uid, name = null, associated_app_name = '' }) { + const $el = $('
'); + $el.attr('data-type', is_dir ? 'directory' : 'file'); + $el.attr('data-is_dir', is_dir ? '1' : '0'); + $el.attr('data-path', path); + $el.attr('data-uid', uid || ''); + $el.attr('data-name', name || path.split('/').pop()); + $el.attr('data-associated_app_name', associated_app_name); + $el.attr('data-is_shortcut', '0'); + $el.attr('data-shortcut_to', ''); + $el.attr('data-shortcut_to_path', ''); + return $el; +} + $(document).on('click', '.window-sidebar-item', async function(e){ const el_window = $(this).closest('.window'); const parent_win_id = $(el_window).attr('data-id'); @@ -2550,50 +2680,157 @@ $(document).on('contextmenu taphold', '.window-sidebar-item', function(event){ event.preventDefault(); event.stopPropagation(); - // todo - // $(this).addClass('window-sidebar-item-highlighted'); const item = this; - UIContextMenu({ - parent_element: $(this), - items: [ - //-------------------------------------------------- - // Open - //-------------------------------------------------- - { - html: "Open", - onClick: function(){ - $(item).trigger('click'); + const $el = $(this); + const item_path = $el.attr('data-path'); + + // Get file/folder metadata first, then create the context menu + puter.fs.stat(item_path, async function(fsentry) { + + // Create context menu after we know if it's a file or folder + UIContextMenu({ + parent_element: $(item), + items: (function() { + const menu_items = []; + + //-------------------------------------------------- + // Open + //-------------------------------------------------- + menu_items.push({ + html: "Open", + onClick: function() { + $(item).trigger('click'); + } + }); + + // -------------------------------------------------- + // Open in New Window (only for folders) + // -------------------------------------------------- + if (fsentry.is_dir) { + menu_items.push({ + html: "Open in New Window", + onClick: async function() { + UIWindow({ + path: item_path, + title: path.basename(item_path), + icon: await item_icon({is_dir: true, path: item_path}), + is_dir: true, + app: 'explorer', + }); + } + }); } - }, - //-------------------------------------------------- - // Open in New Window - //-------------------------------------------------- - { - html: "Open in New Window", - onClick: async function(){ - let item_path = $(item).attr('data-path'); - - UIWindow({ - path: item_path, - title: path.basename(item_path), - icon: await item_icon({is_dir: true, path: item_path}), - // todo - // uid: $(el_item).attr('data-uid'), - is_dir: true, - // todo - // sort_by: $(el_item).attr('data-sort_by'), - app: 'explorer', - // top: options.maximized ? 0 : undefined, - // left: options.maximized ? 0 : undefined, - // height: options.maximized ? `calc(100% - ${window.taskbar_height + 1}px)` : undefined, - // width: options.maximized ? `100%` : undefined, - }); + + //-------------------------------------------------- + // Remove from Favorites (if applicable) and rebuild side bar + //-------------------------------------------------- + let path2 = item_path; + if (path2) { + path2 = path2.replace(/\\/g, '/'); + if (path2.charAt(0) !== '/') { + path2 = '/' + path2; + } + + const favorites = JSON.parse(window.sidebar_items || "[]"); + const is_favorite = favorites.some(fav => fav.path === path2); + + const is_system_path = [ + window.home_path, + window.docs_path, + window.public_path, + window.pictures_path, + window.desktop_path, + window.videos_path + ].includes(path2); + + if (is_favorite && !is_system_path) { + menu_items.push({ + html: 'Remove From Favorites', + onClick: function() { + const updated = favorites.filter(fav => fav.path !== path2); + window.sidebar_items = JSON.stringify(updated); + localStorage.setItem("sidebar_items", JSON.stringify(updated)); + + rebuild_all_sidebars(); + } + }); + } } + + return menu_items; + })() + }); + }); + + return false; // Prevent default context menu from showing +}); + +//Rebuilds all sidebar favorites lists in all windows +function rebuild_all_sidebars() { + $('.window-sidebar').each(function() { + + // Generate new sidebar HTML + let h = ''; + h += `

${i18n('favorites')}

`; + + + // Parse all items + let items = JSON.parse(window.sidebar_items); + for(let item of items) { + let icon; + var filename = item.name; + + if(!window.sidebar_items){ + h += `
${i18n('home')}
`; + h += `
${i18n('documents')}
`; + h += `
${i18n('public')}
`; + h += `
${i18n('pictures')}
`; + h += `
${i18n('desktop')}
`; + h += `
${i18n('videos')}
`; + }else{ + if(item.path === window.home_path) + icon = window.icons['sidebar-folder-home.svg']; + else if(item.path === window.docs_path) + icon = window.icons['sidebar-folder-documents.svg']; + else if(item.path === window.public_path) + icon = window.icons['sidebar-folder-public.svg']; + else if(item.path === window.pictures_path) + icon = window.icons['sidebar-folder-pictures.svg']; + else if(item.path === window.desktop_path) + icon = window.icons['sidebar-folder-desktop.svg']; + else if(item.path === window.videos_path) + icon = window.icons['sidebar-folder-videos.svg']; + else if (item.type === 'folder') { + icon = window.icons['sidebar-folder.svg']; + } else if(filename && filename.includes('.')) { + // Get the extension type + var extension = filename.split('.').pop().toLowerCase(); + if(extension == 'txt') { + iconName = 'file.svg'; + } else { + // Create the SVG icon name string + var iconName = `file-${extension}.svg`; + } + icon = window.icons[iconName]; + } else { + //default folder icon + icon = window.icons['sidebar-folder.svg']; + } + + // Get the current window's path + const current_window = $(this).closest('.ui-window'); + const current_path = current_window.data('path') || ''; + + h += `
${html_encode(item.name)}
`; } - ] + + // Replace the sidebar content + $(this).html(h); + } + }); - return false; -}) +} + $(document).on('dblclick', '.window .ui-resizable-handle', function(e){ let el_window = $(this).closest('.window'); @@ -3721,4 +3958,4 @@ async function saveSidebarOrder(order) { } } -export default UIWindow; +export default UIWindow; \ No newline at end of file diff --git a/src/gui/src/helpers.js b/src/gui/src/helpers.js index b8cc32a550..27d07ce877 100644 --- a/src/gui/src/helpers.js +++ b/src/gui/src/helpers.js @@ -803,6 +803,13 @@ window.create_folder = async(basedir, appendto_element)=>{ // create folder try{ + //Modified the path so that it works correctly + //for Windows developpers as well + dirname = dirname.replace(/\\/g, '/') + if (dirname.charAt(0) !== '/') { + dirname = '/' + dirname; + } + await puter.fs.mkdir({ path: dirname + '/'+folder_name, rename: true,