diff --git a/src/gui/src/UI/UIDesktop.js b/src/gui/src/UI/UIDesktop.js index c9014c0e..2d41dc6e 100644 --- a/src/gui/src/UI/UIDesktop.js +++ b/src/gui/src/UI/UIDesktop.js @@ -563,6 +563,11 @@ async function UIDesktop(options){ } }) + // load window sidebar items from KV + puter.kv.get("sidebar_items").then(async (val) => { + window.sidebar_items = val; + }) + // Get menubar style puter.kv.get('menubar_style').then(async (val) => { let value = val; diff --git a/src/gui/src/UI/UIWindow.js b/src/gui/src/UI/UIWindow.js index 14e013d8..26437f7b 100644 --- a/src/gui/src/UI/UIWindow.js +++ b/src/gui/src/UI/UIWindow.js @@ -265,12 +265,35 @@ async function UIWindow(options) { >`; // favorites h += `

${i18n('favorites')}

`; - h += `
${i18n('home')}
`; - h += `
${i18n('documents')}
`; - h += `
${i18n('public')}
`; - h += `
${i18n('pictures')}
`; - h += `
${i18n('desktop')}
`; - h += `
${i18n('videos')}
`; + // 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)}
`; + } + } h += ``; } @@ -434,6 +457,7 @@ async function UIWindow(options) { // Append $(el_body).append(h); + // disable_parent_window if(options.disable_parent_window && options.parent_uuid !== null){ const $el_parent_window = $(`.window[data-element_uuid="${options.parent_uuid}"]`); @@ -2369,6 +2393,70 @@ async function UIWindow(options) { }); }) + //-------------------------------------------------- + // Sidebar sortable + //-------------------------------------------------- + if(options.is_dir && !isMobile.phone){ + loadSavedSidebarOrder(el_window); + const $sidebar = $(el_window).find('.window-sidebar'); + + $sidebar.sortable({ + items: '.window-sidebar-item:not(.window-sidebar-title)', // More specific selector + connectWith: '.window-sidebar', + cursor: 'move', + axis: 'y', + distance: 5, + containment: 'parent', + placeholder: 'window-sidebar-item-placeholder', + tolerance: 'pointer', + helper: 'clone', + opacity: 0.8, + + start: function(event, ui) { + // Add dragging class + ui.item.addClass('window-sidebar-item-dragging'); + + // Create placeholder styling + ui.placeholder.css({ + 'height': ui.item.height(), + 'visibility': 'visible', + }); + }, + + sort: function(event, ui) { + // Ensure the helper follows the cursor properly + ui.helper.css('pointer-events', 'none'); + }, + + stop: function(event, ui) { + // Remove dragging class + ui.item.removeClass('window-sidebar-item-dragging'); + + // Get the new order + const newOrder = $sidebar.find('.window-sidebar-item').map(function() { + return { + path: $(this).attr('data-path'), + name: $(this).text().trim() + }; + }).get(); + + // Save the new order + saveSidebarOrder(newOrder); + } + }).disableSelection(); // Prevent text selection while dragging + + // Make the sortable operation more responsive + $sidebar.on('mousedown', '.window-sidebar-item', function(e) { + if (!$(this).hasClass('window-sidebar-title')) { + $(this).addClass('grabbing'); + } + }); + + $sidebar.on('mouseup mouseleave', '.window-sidebar-item', function() { + $(this).removeClass('grabbing'); + }); + } + //set styles $(el_window_body).css(options.body_css); @@ -3575,4 +3663,50 @@ document.addEventListener('scroll', function (event) { } }, true); +// Function to save sidebar order to user preferences +async function saveSidebarOrder(order) { + try { + await puter.kv.set({ + key: "sidebar_items", + value: JSON.stringify(order) + }); + + // Save to window object for quick access + window.sidebar_items = JSON.stringify(order); + } catch(err) { + console.error('Error saving sidebar order:', err); + } +} + +// Function to load and apply saved sidebar order +async function loadSavedSidebarOrder(el_window) { + setTimeout(async () => { + try { + // Load saved sidebar order + let savedOrder = window.sidebar_items + + // If not found in window object, try to get from KV + if(!savedOrder){ + savedOrder = await puter.kv.get("sidebar_items"); + } + + // If found, apply the order + if (savedOrder && savedOrder !== 'null' && savedOrder !== 'undefined') { + const order = JSON.parse(savedOrder); + const $sidebar = $(el_window).find('.window-sidebar'); + + // Reorder items according to saved order + order.forEach(item => { + const $item = $sidebar.find(`.window-sidebar-item[data-path="${item.path}"]`); + if ($item.length) { + $item.appendTo($sidebar); + } + }); + } + } catch(err) { + console.error('Error loading sidebar order:', err); + } +}, 1000); +} + export default UIWindow; diff --git a/src/gui/src/css/style.css b/src/gui/src/css/style.css index d27c9856..4025a9a3 100644 --- a/src/gui/src/css/style.css +++ b/src/gui/src/css/style.css @@ -1242,7 +1242,7 @@ span.header-sort-icon img { cursor: pointer; } -.window-sidebar-item { +.window-sidebar-item, .window-sidebar-item.grabbing { margin-bottom: 6px; margin-top: 2px; padding: 4px; @@ -1262,6 +1262,34 @@ span.header-sort-icon img { background-color: #fefeff; } +.window-sidebar-item.grabbing, .window-sidebar-item.ui-sortable-helper{ + background-color: none !important; + height: 23px !important; + /* width: 100% !important; */ +} +.window-sidebar-item-placeholder{ + height: 27px !important; +} +.window-sidebar-item { + cursor: pointer; + user-select: none; +} +.window-sidebar-item:not(.window-sidebar-title):hover { + cursor: grab; +} +.window-sidebar-item.grabbing { + cursor: grabbing !important; +} +.window-sidebar-item-dragging { + background-color: #f5f5f5 !important; + opacity: 0.8; + cursor: grabbing; +} +.ui-sortable-helper { + background: white !important; + box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important; +} + .window-sidebar-item-icon { width: 14px; height: 14px; diff --git a/src/gui/src/icons/sidebar-folder.svg b/src/gui/src/icons/sidebar-folder.svg new file mode 100644 index 00000000..54b66cc7 --- /dev/null +++ b/src/gui/src/icons/sidebar-folder.svg @@ -0,0 +1,8 @@ + + + + + + + +