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 += `
`;
+
+ // 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 += ``;
+ h += ``;
+ h += ``;
+ h += ``;
+ h += ``;
+ h += ``;
+ }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 += ``;
+ }
+
+ // 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 += ``;
}
@@ -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 += ``;
+
+
+ // 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 += ``;
+ h += ``;
+ h += ``;
+ h += ``;
+ h += ``;
+ h += ``;
+ }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 += ``;
}
- ]
+
+ // 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,