소스 검색

dev: factor out menu items functions in UIItem

The large if...else block for single-item vs multi-item selection
context menu items makes it difficult to extract common behaviors
between the two. For example, the "create shortcut" menu item could be
added by the same code snippet in both cases, and that snippet might
have conditional logic for the number of items (with 1 as a special
case).

The reason to move branching logic to the code snippets that handle
individual menu items is because the previous change to fix shortcuts in
shared directories revealed cross-cutting concerns in branching logic.
Since both single-item and multi-item "create shortcut" options both
have branching to handle the shared directory case, they are now more
similar than different.
KernelDeimos 6 일 전
부모
커밋
aa049b2c5a
2개의 변경된 파일653개의 추가작업 그리고 627개의 파일을 삭제
  1. 13 627
      src/gui/src/UI/UIItem.js
  2. 640 0
      src/gui/src/UI/lib/ui_item.js

+ 13 - 627
src/gui/src/UI/UIItem.js

@@ -17,18 +17,14 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-import UIWindowShare from './UIWindowShare.js';
-import UIWindowPublishWebsite from './UIWindowPublishWebsite.js';
-import UIWindowItemProperties from './UIWindowItemProperties.js';
-import UIWindowSaveAccount from './UIWindowSaveAccount.js';
 import UIPopover from './UIPopover.js';
-import UIWindowEmailConfirmationRequired from './UIWindowEmailConfirmationRequired.js';
 import UIContextMenu from './UIContextMenu.js'
 import UIAlert from './UIAlert.js'
 import path from "../lib/path.js"
 import truncate_filename from '../helpers/truncate_filename.js';
 import launch_app from "../helpers/launch_app.js"
 import open_item from "../helpers/open_item.js"
+import { add_multiple_select_menu_items, add_single_select_menu_items } from './lib/ui_item.js';
 
 function UIItem(options){
     const matching_appendto_count = $(options.appendTo).length;
@@ -755,635 +751,25 @@ function UIItem(options){
             return;
 
         event.preventDefault();
-        let menu_items;
+        let menu_items = [];
         const $selected_items = $(el_item).closest('.item-container').find('.item-selected').not(el_item).addBack();
-        // -------------------------------------------------------
+
         // Multiple items selected
-        // -------------------------------------------------------
         if($selected_items.length > 1){
-            const are_trashed = $selected_items.attr('data-path').startsWith(window.trash_path + '/');
-            menu_items = []
-            // -------------------------------------------
-            // Restore
-            // -------------------------------------------
-            if(are_trashed){
-                menu_items.push({
-                    html: i18n('restore'),
-                    onClick: function(){
-                        $selected_items.each(function() {
-                            const ell = this;
-                            let metadata = $(ell).attr('data-metadata') === '' ? {} : JSON.parse($(ell).attr('data-metadata'))
-                            window.move_items([ell], path.dirname(metadata.original_path));
-                        })
-                    }
-                });
-                // -------------------------------------------
-                // -
-                // -------------------------------------------
-                menu_items.push('-');
-            }
-            if(!are_trashed){
-                menu_items.push({
-                    html: 'Share With…',
-                    onClick: async function(){
-                        if(window.user.is_temp && 
-                            !await UIWindowSaveAccount({
-                                send_confirmation_code: true,
-                                message: 'Please create an account to proceed.',
-                                window_options: {
-                                    backdrop: true,
-                                    close_on_backdrop_click: false,
-                                }                                
-                            }))
-                            return;
-                        else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired())
-                            return;
-
-                        let items = [];
-                        $selected_items.each(function() {
-                            const ell = this;
-                            items.push({uid: $(ell).attr('data-uid'), path: $(ell).attr('data-path'), icon: $(ell).find('.item-icon img').attr('src'), name: $(ell).attr('data-name')});
-                        })
-                        UIWindowShare(items);
-                    }
-                })
-                // -------------------------------------------
-                // -
-                // -------------------------------------------
-                menu_items.push({ is_divider: true });
-
-                // -------------------------------------------
-                // Donwload
-                // -------------------------------------------
-                menu_items.push({
-                    html: i18n('download'),
-                    onClick: async function(){
-                        let items = [];
-                        for (let index = 0; index < $selected_items.length; index++) {
-                            items.push($selected_items[index]);
-                        }
-
-                        window.zipItems(items, path.dirname($(el_item).attr('data-path')), true);
-                    }
-                });
-                // -------------------------------------------
-                // Zip
-                // -------------------------------------------
-                menu_items.push({
-                    html: i18n('zip'),
-                    onClick: async function(){
-                        let items = [];
-                        for (let index = 0; index < $selected_items.length; index++) {
-                            items.push($selected_items[index]);
-                        }
-
-                        window.zipItems(items, path.dirname($(el_item).attr('data-path')), false);
-                    }
-                });
-                // -------------------------------------------
-                // -
-                // -------------------------------------------
-                menu_items.push('-');
-            }
-            // -------------------------------------------
-            // Cut
-            // -------------------------------------------
-            menu_items.push({
-                html: i18n('cut'),
-                onClick: function(){
-                    window.clipboard_op= 'move';
-                    window.clipboard = [];
-                    $selected_items.each(function() {
-                        const ell = this;
-                        window.clipboard.push($(ell).attr('data-path'));
-                    })
-                }
+            add_multiple_select_menu_items(menu_items, {
+                $selected_items,
+                el_item,
+                is_shared_with_me,
             });
-            // -------------------------------------------
-            // Copy
-            // -------------------------------------------
-            if(!are_trashed){
-                menu_items.push({
-                    html: i18n('copy'),
-                    onClick: function(){
-                        window.clipboard_op= 'copy';
-                        window.clipboard = [];
-                        $selected_items.each(function() {
-                            const ell = this;
-                            window.clipboard.push({path: $(ell).attr('data-path')});
-                        })
-                    }
-                });
-            }
-            // -------------------------------------------
-            // -
-            // -------------------------------------------
-            menu_items.push('-');
-            // -------------------------------------------
-            // Delete Permanently
-            // -------------------------------------------
-            if(are_trashed){
-                menu_items.push({
-                    html: i18n('delete_permanently'),
-                    onClick: async function(){
-                        const alert_resp = await UIAlert({
-                            message: i18n('confirm_delete_multiple_items'),
-                            buttons:[
-                                {
-                                    label: i18n('delete'),
-                                    type: 'primary',
-                                },
-                                {
-                                    label: i18n('cancel')
-                                },
-                            ]
-                        })
-                        if((alert_resp) === 'Delete'){
-                            for (let index = 0; index < $selected_items.length; index++) {
-                                const element = $selected_items[index];
-                                await window.delete_item(element);
-                            }
-                            const trash = await puter.fs.stat(window.trash_path);
-
-                            // update other clients
-                            if(window.socket){
-                                window.socket.emit('trash.is_empty', {is_empty: trash.is_empty});
-                            }
-
-                            if(trash.is_empty){
-                                $(`.item[data-path="${html_encode(window.trash_path)}" i], .item[data-shortcut_to_path="${window.trash_path}" i]`).find('.item-icon > img').attr('src', window.icons['trash.svg']);
-                                $(`.window[data-path="${html_encode(window.trash_path)}"]`).find('.window-head-icon').attr('src', window.icons['trash.svg']);
-                            }            
-                        }
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Create Shortcut
-            // -------------------------------------------
-            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'));
-                            // Trash on Desktop is a special case
-                            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')), 
-                                $(this).attr('data-is_dir') === '1', 
-                                base_dir, 
-                                $(this).closest('.item-container'), 
-                                $(this).attr('data-shortcut_to') === '' ? $(this).attr('data-uid') : $(this).attr('data-shortcut_to'),
-                                $(this).attr('data-shortcut_to_path') === '' ? $(this).attr('data-path') : $(this).attr('data-shortcut_to_path'),
-                            );
-                        })
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Delete
-            // -------------------------------------------
-            if(!are_trashed){
-                menu_items.push({
-                    html: i18n('delete'),
-                    onClick: async function(){
-                        window.move_items($selected_items, window.trash_path);
-                    }
-                });
-            }
-
         }
-
-        // -------------------------------------------------------
         // One item selected
-        // -------------------------------------------------------   
         else{
-            const is_trash = $(el_item).attr('data-path') === window.trash_path || $(el_item).attr('data-shortcut_to_path') === window.trash_path;
-            menu_items = [];
-            // -------------------------------------------
-            // Open
-            // -------------------------------------------
-            if(!is_trashed){
-                menu_items.push({
-                    html: i18n('open'),
-                    onClick: function(){
-                        open_item({item: el_item});
-                    }
-                });
-
-                // -------------------------------------------
-                // -
-                // -------------------------------------------
-                if(options.associated_app_name || is_trash)
-                    menu_items.push('-');
-            }
-            // -------------------------------------------
-            // Open With
-            // -------------------------------------------
-            if(!is_trashed && !is_trash  && (options.associated_app_name === null || options.associated_app_name === undefined)){
-                let items = [];
-                if(!options.suggested_apps || options.suggested_apps.length === 0){
-                    // try to find suitable apps
-                    const suitable_apps = await window.suggest_apps_for_fsentry({
-                        uid: options.uid,
-                        path: options.path,
-                    });
-                    if(suitable_apps && suitable_apps.length > 0){
-                        options.suggested_apps = suitable_apps;
-                    }
-                }
-
-                if(options.suggested_apps && options.suggested_apps.length > 0){
-                    for (let index = 0; index < options.suggested_apps.length; index++) {
-                        const suggested_app = options.suggested_apps[index];
-                        if ( ! suggested_app ) {
-                            console.warn(`suggested_app is null`, options.suggested_apps, index);
-                            continue;
-                        }
-                        items.push({
-                            html: suggested_app.title,
-                            icon: `<img src="${html_encode(suggested_app.icon ?? window.icons['app.svg'])}" style="width:16px; height: 16px; margin-bottom: -4px;">`,
-                            onClick: async function(){
-                                var extension = path.extname($(el_item).attr('data-path')).toLowerCase();
-                                if(
-                                    window.user_preferences[`default_apps${extension}`] !== suggested_app.name
-                                    && 
-                                    (
-                                        (!window.user_preferences[`default_apps${extension}`] && index > 0)
-                                        || 
-                                        (window.user_preferences[`default_apps${extension}`])
-                                    )
-                                ){
-                                    const alert_resp = await UIAlert({
-                                        message: `${i18n('change_always_open_with')} ` + html_encode(suggested_app.title) + '?',
-                                        body_icon: suggested_app.icon,
-                                        buttons:[
-                                            {
-                                                label: i18n('yes'),
-                                                type: 'primary',
-                                                value: 'yes'
-                                            },
-                                            {
-                                                label: i18n('no')
-                                            },
-                                        ]
-                                    })
-                                    if((alert_resp) === 'yes'){
-                                        window.user_preferences['default_apps' + extension] = suggested_app.name;
-                                        window.mutate_user_preferences(window.user_preferences);
-                                    }
-                                }
-                                launch_app({
-                                    name: suggested_app.name,
-                                    file_path: $(el_item).attr('data-path'),
-                                    window_title: $(el_item).attr('data-name'),
-                                    file_uid: $(el_item).attr('data-uid'),
-                                });
-                            }
-                        })
-                    }
-                }else{                    
-                    items.push({
-                        html: 'No suitable apps found',
-                        disabled: true,
-                    });
-                }
-                // add all suitable apps
-                menu_items.push({
-                    html: i18n('open_with'),
-                    items: items,
-                });
-
-                // -------------------------------------------
-                // -- separator --
-                // -------------------------------------------
-                menu_items.push('-');
-            }
-
-            // -------------------------------------------
-            // Open in New Window
-            // (only if the item is on a window)
-            // -------------------------------------------
-            if($(el_item).closest('.window-body').length > 0 && options.is_dir){
-                menu_items.push({
-                    html: i18n('open_in_new_window'),
-                    onClick: function(){
-                        if(options.is_dir){
-                            open_item({item: el_item, new_window: true})
-                        }
-                    }
-                });
-                // -------------------------------------------
-                // -- separator --
-                // -------------------------------------------
-                if(!is_trash && !is_trashed && options.is_dir)
-                    menu_items.push('-');
-            }
-            // -------------------------------------------
-            // Share With…
-            // -------------------------------------------
-            if(!is_trashed && !is_trash){
-                menu_items.push({
-                    html: 'Share With…',
-                    onClick: async function(){
-                        if(window.user.is_temp && 
-                            !await UIWindowSaveAccount({
-                                send_confirmation_code: true,
-                                message: 'Please create an account to proceed.',
-                                window_options: {
-                                    backdrop: true,
-                                    close_on_backdrop_click: false,
-                                }                                
-                            }))
-                            return;
-                        else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired())
-                            return;
-
-                        UIWindowShare([{uid: $(el_item).attr('data-uid'), path: $(el_item).attr('data-path'), name: $(el_item).attr('data-name'), icon: $(el_item_icon).find('img').attr('src')}]);
-                    }
-                });
-            }
-
-            // -------------------------------------------
-            // Publish As Website
-            // -------------------------------------------
-            if(!is_trashed && !is_trash && options.is_dir){
-                menu_items.push({
-                    html: i18n('publish_as_website'),
-                    disabled: !options.is_dir,
-                    onClick: async function () {
-                        if(window.require_email_verification_to_publish_website){
-                            if(window.user.is_temp && 
-                                !await UIWindowSaveAccount({
-                                    send_confirmation_code: true,
-                                    message: 'Please create an account to proceed.',
-                                    window_options: {
-                                        backdrop: true,
-                                        close_on_backdrop_click: false,
-                                    }                                
-                                }))
-                                return;
-                            else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired())
-                                return;
-                        }
-                        UIWindowPublishWebsite(options.uid, $(el_item).attr('data-name'), $(el_item).attr('data-path'));
-                    }
-                });
-
-            }
-            // -------------------------------------------
-            // Deploy As App
-            // -------------------------------------------
-            if(!is_trashed && !is_trash && options.is_dir){
-                menu_items.push({
-                    html: i18n('deploy_as_app'),
-                    disabled: !options.is_dir,
-                    onClick: async function () {
-                        launch_app({
-                            name: 'dev-center',
-                            file_path: $(el_item).attr('data-path'),
-                            file_uid: $(el_item).attr('data-uid'),
-                            params: {
-                                source_path: options.path,
-                            }
-                        })
-                    }
-                });
-
-                menu_items.push('-');
-            }
-
-            // -------------------------------------------
-            // Empty Trash
-            // -------------------------------------------
-            if(is_trash){
-                menu_items.push({
-                    html: i18n('empty_trash'),
-                    onClick: async function(){
-                        window.empty_trash();
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Download
-            // -------------------------------------------
-            if(!is_trash && !is_trashed && (options.associated_app_name === null || options.associated_app_name === undefined)){
-                menu_items.push({
-                    html: i18n('download'),
-                    disabled: options.is_dir && !window.feature_flags.download_directory,
-                    onClick: async function(){
-                        if(options.is_dir)
-                            window.zipItems(el_item, path.dirname($(el_item).attr('data-path')), true);
-                        else
-                            window.trigger_download([options.path]);
-                    }
-                });                
-            }
-            // -------------------------------------------
-            // Zip
-            // -------------------------------------------
-            if(!is_trash && !is_trashed && !$(el_item).attr('data-path').endsWith('.zip')){
-                menu_items.push({
-                    html: i18n('zip'),
-                    onClick: function(){
-                        window.zipItems(el_item, path.dirname($(el_item).attr('data-path')), false);
-                    }
-                })
-            }
-            // -------------------------------------------
-            // Unzip
-            // -------------------------------------------
-            if(!is_trash && !is_trashed && $(el_item).attr('data-path').endsWith('.zip')){
-                menu_items.push({
-                    html: i18n('unzip'),
-                    onClick: async function(){
-                        let filePath = $(el_item).attr('data-path');
-                        window.unzipItem(filePath)
-                    }
-                })
-            }
-            // -------------------------------------------
-            // Restore
-            // -------------------------------------------
-            if(is_trashed){
-                menu_items.push({
-                    html: i18n('restore'),
-                    onClick: async function(){
-                        let metadata = $(el_item).attr('data-metadata') === '' ? {} : JSON.parse($(el_item).attr('data-metadata'))
-                        window.move_items([el_item], path.dirname(metadata.original_path));
-                    }
-                });
-            }
-            // -------------------------------------------
-            // -
-            // -------------------------------------------
-            if(!is_trash && (options.associated_app_name === null || options.associated_app_name === undefined))
-                menu_items.push('-');
-            // -------------------------------------------
-            // Cut
-            // -------------------------------------------
-            if($(el_item).attr('data-immutable') === '0' && !is_shared_with_me){
-                menu_items.push({
-                    html: i18n('cut'),
-                    onClick: function(){
-                        window.clipboard_op= 'move';
-                        window.clipboard= [options.path];
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Copy
-            // -------------------------------------------
-            if(!is_trashed && !is_trash){
-                menu_items.push({
-                    html: i18n('copy'),
-                    onClick: function(){
-                        window.clipboard_op= 'copy';
-                        window.clipboard= [{path: options.path}];
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Paste Into Folder
-            // -------------------------------------------
-            if($(el_item).attr('data-is_dir') === '1' && !is_trashed && !is_trash){
-                menu_items.push({
-                    html: i18n('paste_into_folder'),
-                    disabled: window.clipboard.length > 0 ? false : true,
-                    onClick: function(){
-                        if(window.clipboard_op === 'copy')
-                            window.copy_clipboard_items($(el_item).attr('data-path'), null);
-                        else if(window.clipboard_op === 'move')
-                            window.move_clipboard_items(null, $(el_item).attr('data-path'))
-                    }
-                })
-            }
-            // -------------------------------------------
-            // -
-            // -------------------------------------------
-            if($(el_item).attr('data-immutable') === '0' && !is_trash){
-                menu_items.push('-')
-            }
-            // -------------------------------------------
-            // Create Shortcut
-            // -------------------------------------------
-            if(!is_trashed && window.feature_flags.create_shortcut){
-                menu_items.push({
-                    html: is_shared_with_me ? i18n('create_desktop_shortcut') : i18n('create_shortcut'),
-                    onClick: async function(){
-                        let base_dir = path.dirname($(el_item).attr('data-path'));
-                        // Trash on Desktop is a special case
-                        if($(el_item).attr('data-path') && $(el_item).closest('.item-container').attr('data-path') === window.desktop_path){
-                            base_dir = window.desktop_path;
-                        }
-
-                        if ( is_shared_with_me ) base_dir = window.desktop_path;
-
-                        window.create_shortcut(
-                            path.basename($(el_item).attr('data-path')), 
-                            options.is_dir, 
-                            base_dir, 
-                            options.appendTo, 
-                            options.shortcut_to === '' ? options.uid : options.shortcut_to,
-                            options.shortcut_to_path === '' ? options.path : options.shortcut_to_path,
-                        );
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Delete
-            // -------------------------------------------
-            if($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_shared_with_me){
-                menu_items.push({
-                    html: i18n('delete'),
-                    onClick: async function(){
-                        window.move_items([el_item], window.trash_path);
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Delete Permanently
-            // -------------------------------------------
-            if(is_trashed){
-                menu_items.push({
-                    html: i18n('delete_permanently'),
-                    onClick: async function(){
-                        const alert_resp = await UIAlert({
-                            message: i18n('confirm_delete_single_item'),
-                            buttons:[
-                                {
-                                    label: i18n('delete'),
-                                    type: 'primary',
-                                },
-                                {
-                                    label: i18n('cancel')
-                                },
-                            ]
-                        })
-
-                        if((alert_resp) === 'Delete'){
-                            await window.delete_item(el_item);
-                            // check if trash is empty
-                            const trash = await puter.fs.stat(window.trash_path);
-                            // update other clients
-                            if(window.socket){
-                                window.socket.emit('trash.is_empty', {is_empty: trash.is_empty});
-                            }
-                            // update this client
-                            if(trash.is_empty){
-                                $(`.item[data-path="${html_encode(window.trash_path)}" i], .item[data-shortcut_to_path="${html_encode(window.trash_path)}" i]`).find('.item-icon > img').attr('src', window.icons['trash.svg']);
-                                $(`.window[data-path="${window.trash_path}"]`).find('.window-head-icon').attr('src', window.icons['trash.svg']);
-                            }            
-                        }
-                    }
-                });
-            }
-            // -------------------------------------------
-            // Rename
-            // -------------------------------------------
-            if($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_trash){
-                menu_items.push({
-                    html: i18n('rename'),
-                    onClick: function(){
-                        window.activate_item_name_editor(el_item)
-                    }
-                });
-            }
-            // -------------------------------------------
-            // -
-            // -------------------------------------------
-            menu_items.push('-');
-            // -------------------------------------------
-            // Properties
-            // -------------------------------------------
-            menu_items.push({
-                html: i18n('properties'),
-                onClick: function(){
-                    let window_height = 500;
-                    let window_width = 450;
-
-                    let left = $(el_item).position().left + $(el_item).width();
-                    left = left > (window.innerWidth - window_width)? (window.innerWidth - window_width) : left;
-
-                    let top = $(el_item).position().top + $(el_item).height();
-                    top = top > (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height))? (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height)) : top;
-
-                    UIWindowItemProperties(
-                        $(el_item).attr('data-name'), 
-                        $(el_item).attr('data-path'), 
-                        $(el_item).attr('data-uid'),
-                        left,
-                        top,
-                        window_width,
-                        window_height,
-                    );
-                }
+            await add_single_select_menu_items(menu_items, {
+                options,
+                el_item,
+                is_trashed,
+                is_shared_with_me,
+                el_item_icon,
             });
         }     
         

+ 640 - 0
src/gui/src/UI/lib/ui_item.js

@@ -0,0 +1,640 @@
+import UIWindowShare from '../UIWindowShare.js';
+import UIWindowPublishWebsite from '../UIWindowPublishWebsite.js';
+import UIWindowItemProperties from '../UIWindowItemProperties.js';
+import UIWindowSaveAccount from '../UIWindowSaveAccount.js';
+import UIWindowEmailConfirmationRequired from '../UIWindowEmailConfirmationRequired.js';
+import UIAlert from '../UIAlert.js'
+import path from "../../lib/path.js"
+import launch_app from "../../helpers/launch_app.js"
+import open_item from "../../helpers/open_item.js"
+
+export const add_multiple_select_menu_items = (menu_items, {
+    $selected_items,
+    el_item,
+    is_shared_with_me,
+}) => {
+    const are_trashed = $selected_items.attr('data-path').startsWith(window.trash_path + '/');
+    // -------------------------------------------
+    // Restore
+    // -------------------------------------------
+    if(are_trashed){
+        menu_items.push({
+            html: i18n('restore'),
+            onClick: function(){
+                $selected_items.each(function() {
+                    const ell = this;
+                    let metadata = $(ell).attr('data-metadata') === '' ? {} : JSON.parse($(ell).attr('data-metadata'))
+                    window.move_items([ell], path.dirname(metadata.original_path));
+                })
+            }
+        });
+        // -------------------------------------------
+        // -
+        // -------------------------------------------
+        menu_items.push('-');
+    }
+    if(!are_trashed){
+        menu_items.push({
+            html: 'Share With…',
+            onClick: async function(){
+                if(window.user.is_temp && 
+                    !await UIWindowSaveAccount({
+                        send_confirmation_code: true,
+                        message: 'Please create an account to proceed.',
+                        window_options: {
+                            backdrop: true,
+                            close_on_backdrop_click: false,
+                        }                                
+                    }))
+                    return;
+                else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired())
+                    return;
+
+                let items = [];
+                $selected_items.each(function() {
+                    const ell = this;
+                    items.push({uid: $(ell).attr('data-uid'), path: $(ell).attr('data-path'), icon: $(ell).find('.item-icon img').attr('src'), name: $(ell).attr('data-name')});
+                })
+                UIWindowShare(items);
+            }
+        })
+        // -------------------------------------------
+        // -
+        // -------------------------------------------
+        menu_items.push({ is_divider: true });
+
+        // -------------------------------------------
+        // Donwload
+        // -------------------------------------------
+        menu_items.push({
+            html: i18n('download'),
+            onClick: async function(){
+                let items = [];
+                for (let index = 0; index < $selected_items.length; index++) {
+                    items.push($selected_items[index]);
+                }
+
+                window.zipItems(items, path.dirname($(el_item).attr('data-path')), true);
+            }
+        });
+        // -------------------------------------------
+        // Zip
+        // -------------------------------------------
+        menu_items.push({
+            html: i18n('zip'),
+            onClick: async function(){
+                let items = [];
+                for (let index = 0; index < $selected_items.length; index++) {
+                    items.push($selected_items[index]);
+                }
+
+                window.zipItems(items, path.dirname($(el_item).attr('data-path')), false);
+            }
+        });
+        // -------------------------------------------
+        // -
+        // -------------------------------------------
+        menu_items.push('-');
+    }
+    // -------------------------------------------
+    // Cut
+    // -------------------------------------------
+    menu_items.push({
+        html: i18n('cut'),
+        onClick: function(){
+            window.clipboard_op= 'move';
+            window.clipboard = [];
+            $selected_items.each(function() {
+                const ell = this;
+                window.clipboard.push($(ell).attr('data-path'));
+            })
+        }
+    });
+    // -------------------------------------------
+    // Copy
+    // -------------------------------------------
+    if(!are_trashed){
+        menu_items.push({
+            html: i18n('copy'),
+            onClick: function(){
+                window.clipboard_op= 'copy';
+                window.clipboard = [];
+                $selected_items.each(function() {
+                    const ell = this;
+                    window.clipboard.push({path: $(ell).attr('data-path')});
+                })
+            }
+        });
+    }
+    // -------------------------------------------
+    // -
+    // -------------------------------------------
+    menu_items.push('-');
+    // -------------------------------------------
+    // Delete Permanently
+    // -------------------------------------------
+    if(are_trashed){
+        menu_items.push({
+            html: i18n('delete_permanently'),
+            onClick: async function(){
+                const alert_resp = await UIAlert({
+                    message: i18n('confirm_delete_multiple_items'),
+                    buttons:[
+                        {
+                            label: i18n('delete'),
+                            type: 'primary',
+                        },
+                        {
+                            label: i18n('cancel')
+                        },
+                    ]
+                })
+                if((alert_resp) === 'Delete'){
+                    for (let index = 0; index < $selected_items.length; index++) {
+                        const element = $selected_items[index];
+                        await window.delete_item(element);
+                    }
+                    const trash = await puter.fs.stat(window.trash_path);
+
+                    // update other clients
+                    if(window.socket){
+                        window.socket.emit('trash.is_empty', {is_empty: trash.is_empty});
+                    }
+
+                    if(trash.is_empty){
+                        $(`.item[data-path="${html_encode(window.trash_path)}" i], .item[data-shortcut_to_path="${window.trash_path}" i]`).find('.item-icon > img').attr('src', window.icons['trash.svg']);
+                        $(`.window[data-path="${html_encode(window.trash_path)}"]`).find('.window-head-icon').attr('src', window.icons['trash.svg']);
+                    }            
+                }
+            }
+        });
+    }
+    // -------------------------------------------
+    // Create Shortcut
+    // -------------------------------------------
+    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'));
+                    // Trash on Desktop is a special case
+                    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')), 
+                        $(this).attr('data-is_dir') === '1', 
+                        base_dir, 
+                        $(this).closest('.item-container'), 
+                        $(this).attr('data-shortcut_to') === '' ? $(this).attr('data-uid') : $(this).attr('data-shortcut_to'),
+                        $(this).attr('data-shortcut_to_path') === '' ? $(this).attr('data-path') : $(this).attr('data-shortcut_to_path'),
+                    );
+                })
+            }
+        });
+    }
+    // -------------------------------------------
+    // Delete
+    // -------------------------------------------
+    if(!are_trashed){
+        menu_items.push({
+            html: i18n('delete'),
+            onClick: async function(){
+                window.move_items($selected_items, window.trash_path);
+            }
+        });
+    }
+};
+
+export const add_single_select_menu_items = async (menu_items, {
+    options,
+    el_item,
+    is_trashed,
+    is_shared_with_me,
+    el_item_icon,
+}) => {
+    const is_trash = $(el_item).attr('data-path') === window.trash_path || $(el_item).attr('data-shortcut_to_path') === window.trash_path;
+    // -------------------------------------------
+    // Open
+    // -------------------------------------------
+    if(!is_trashed){
+        menu_items.push({
+            html: i18n('open'),
+            onClick: function(){
+                open_item({item: el_item});
+            }
+        });
+
+        // -------------------------------------------
+        // -
+        // -------------------------------------------
+        if(options.associated_app_name || is_trash)
+            menu_items.push('-');
+    }
+    // -------------------------------------------
+    // Open With
+    // -------------------------------------------
+    if(!is_trashed && !is_trash  && (options.associated_app_name === null || options.associated_app_name === undefined)){
+        let items = [];
+        if(!options.suggested_apps || options.suggested_apps.length === 0){
+            // try to find suitable apps
+            const suitable_apps = await window.suggest_apps_for_fsentry({
+                uid: options.uid,
+                path: options.path,
+            });
+            if(suitable_apps && suitable_apps.length > 0){
+                options.suggested_apps = suitable_apps;
+            }
+        }
+
+        if(options.suggested_apps && options.suggested_apps.length > 0){
+            for (let index = 0; index < options.suggested_apps.length; index++) {
+                const suggested_app = options.suggested_apps[index];
+                if ( ! suggested_app ) {
+                    console.warn(`suggested_app is null`, options.suggested_apps, index);
+                    continue;
+                }
+                items.push({
+                    html: suggested_app.title,
+                    icon: `<img src="${html_encode(suggested_app.icon ?? window.icons['app.svg'])}" style="width:16px; height: 16px; margin-bottom: -4px;">`,
+                    onClick: async function(){
+                        var extension = path.extname($(el_item).attr('data-path')).toLowerCase();
+                        if(
+                            window.user_preferences[`default_apps${extension}`] !== suggested_app.name
+                            && 
+                            (
+                                (!window.user_preferences[`default_apps${extension}`] && index > 0)
+                                || 
+                                (window.user_preferences[`default_apps${extension}`])
+                            )
+                        ){
+                            const alert_resp = await UIAlert({
+                                message: `${i18n('change_always_open_with')} ` + html_encode(suggested_app.title) + '?',
+                                body_icon: suggested_app.icon,
+                                buttons:[
+                                    {
+                                        label: i18n('yes'),
+                                        type: 'primary',
+                                        value: 'yes'
+                                    },
+                                    {
+                                        label: i18n('no')
+                                    },
+                                ]
+                            })
+                            if((alert_resp) === 'yes'){
+                                window.user_preferences['default_apps' + extension] = suggested_app.name;
+                                window.mutate_user_preferences(window.user_preferences);
+                            }
+                        }
+                        launch_app({
+                            name: suggested_app.name,
+                            file_path: $(el_item).attr('data-path'),
+                            window_title: $(el_item).attr('data-name'),
+                            file_uid: $(el_item).attr('data-uid'),
+                        });
+                    }
+                })
+            }
+        }else{                    
+            items.push({
+                html: 'No suitable apps found',
+                disabled: true,
+            });
+        }
+        // add all suitable apps
+        menu_items.push({
+            html: i18n('open_with'),
+            items: items,
+        });
+
+        // -------------------------------------------
+        // -- separator --
+        // -------------------------------------------
+        menu_items.push('-');
+    }
+
+    // -------------------------------------------
+    // Open in New Window
+    // (only if the item is on a window)
+    // -------------------------------------------
+    if($(el_item).closest('.window-body').length > 0 && options.is_dir){
+        menu_items.push({
+            html: i18n('open_in_new_window'),
+            onClick: function(){
+                if(options.is_dir){
+                    open_item({item: el_item, new_window: true})
+                }
+            }
+        });
+        // -------------------------------------------
+        // -- separator --
+        // -------------------------------------------
+        if(!is_trash && !is_trashed && options.is_dir)
+            menu_items.push('-');
+    }
+    // -------------------------------------------
+    // Share With…
+    // -------------------------------------------
+    if(!is_trashed && !is_trash){
+        menu_items.push({
+            html: 'Share With…',
+            onClick: async function(){
+                if(window.user.is_temp && 
+                    !await UIWindowSaveAccount({
+                        send_confirmation_code: true,
+                        message: 'Please create an account to proceed.',
+                        window_options: {
+                            backdrop: true,
+                            close_on_backdrop_click: false,
+                        }                                
+                    }))
+                    return;
+                else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired())
+                    return;
+
+                UIWindowShare([{uid: $(el_item).attr('data-uid'), path: $(el_item).attr('data-path'), name: $(el_item).attr('data-name'), icon: $(el_item_icon).find('img').attr('src')}]);
+            }
+        });
+    }
+
+    // -------------------------------------------
+    // Publish As Website
+    // -------------------------------------------
+    if(!is_trashed && !is_trash && options.is_dir){
+        menu_items.push({
+            html: i18n('publish_as_website'),
+            disabled: !options.is_dir,
+            onClick: async function () {
+                if(window.require_email_verification_to_publish_website){
+                    if(window.user.is_temp && 
+                        !await UIWindowSaveAccount({
+                            send_confirmation_code: true,
+                            message: 'Please create an account to proceed.',
+                            window_options: {
+                                backdrop: true,
+                                close_on_backdrop_click: false,
+                            }                                
+                        }))
+                        return;
+                    else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired())
+                        return;
+                }
+                UIWindowPublishWebsite(options.uid, $(el_item).attr('data-name'), $(el_item).attr('data-path'));
+            }
+        });
+
+    }
+    // -------------------------------------------
+    // Deploy As App
+    // -------------------------------------------
+    if(!is_trashed && !is_trash && options.is_dir){
+        menu_items.push({
+            html: i18n('deploy_as_app'),
+            disabled: !options.is_dir,
+            onClick: async function () {
+                launch_app({
+                    name: 'dev-center',
+                    file_path: $(el_item).attr('data-path'),
+                    file_uid: $(el_item).attr('data-uid'),
+                    params: {
+                        source_path: options.path,
+                    }
+                })
+            }
+        });
+
+        menu_items.push('-');
+    }
+
+    // -------------------------------------------
+    // Empty Trash
+    // -------------------------------------------
+    if(is_trash){
+        menu_items.push({
+            html: i18n('empty_trash'),
+            onClick: async function(){
+                window.empty_trash();
+            }
+        });
+    }
+    // -------------------------------------------
+    // Download
+    // -------------------------------------------
+    if(!is_trash && !is_trashed && (options.associated_app_name === null || options.associated_app_name === undefined)){
+        menu_items.push({
+            html: i18n('download'),
+            disabled: options.is_dir && !window.feature_flags.download_directory,
+            onClick: async function(){
+                if(options.is_dir)
+                    window.zipItems(el_item, path.dirname($(el_item).attr('data-path')), true);
+                else
+                    window.trigger_download([options.path]);
+            }
+        });                
+    }
+    // -------------------------------------------
+    // Zip
+    // -------------------------------------------
+    if(!is_trash && !is_trashed && !$(el_item).attr('data-path').endsWith('.zip')){
+        menu_items.push({
+            html: i18n('zip'),
+            onClick: function(){
+                window.zipItems(el_item, path.dirname($(el_item).attr('data-path')), false);
+            }
+        })
+    }
+    // -------------------------------------------
+    // Unzip
+    // -------------------------------------------
+    if(!is_trash && !is_trashed && $(el_item).attr('data-path').endsWith('.zip')){
+        menu_items.push({
+            html: i18n('unzip'),
+            onClick: async function(){
+                let filePath = $(el_item).attr('data-path');
+                window.unzipItem(filePath)
+            }
+        })
+    }
+    // -------------------------------------------
+    // Restore
+    // -------------------------------------------
+    if(is_trashed){
+        menu_items.push({
+            html: i18n('restore'),
+            onClick: async function(){
+                let metadata = $(el_item).attr('data-metadata') === '' ? {} : JSON.parse($(el_item).attr('data-metadata'))
+                window.move_items([el_item], path.dirname(metadata.original_path));
+            }
+        });
+    }
+    // -------------------------------------------
+    // -
+    // -------------------------------------------
+    if(!is_trash && (options.associated_app_name === null || options.associated_app_name === undefined))
+        menu_items.push('-');
+    // -------------------------------------------
+    // Cut
+    // -------------------------------------------
+    if($(el_item).attr('data-immutable') === '0' && !is_shared_with_me){
+        menu_items.push({
+            html: i18n('cut'),
+            onClick: function(){
+                window.clipboard_op= 'move';
+                window.clipboard= [options.path];
+            }
+        });
+    }
+    // -------------------------------------------
+    // Copy
+    // -------------------------------------------
+    if(!is_trashed && !is_trash){
+        menu_items.push({
+            html: i18n('copy'),
+            onClick: function(){
+                window.clipboard_op= 'copy';
+                window.clipboard= [{path: options.path}];
+            }
+        });
+    }
+    // -------------------------------------------
+    // Paste Into Folder
+    // -------------------------------------------
+    if($(el_item).attr('data-is_dir') === '1' && !is_trashed && !is_trash){
+        menu_items.push({
+            html: i18n('paste_into_folder'),
+            disabled: window.clipboard.length > 0 ? false : true,
+            onClick: function(){
+                if(window.clipboard_op === 'copy')
+                    window.copy_clipboard_items($(el_item).attr('data-path'), null);
+                else if(window.clipboard_op === 'move')
+                    window.move_clipboard_items(null, $(el_item).attr('data-path'))
+            }
+        })
+    }
+    // -------------------------------------------
+    // -
+    // -------------------------------------------
+    if($(el_item).attr('data-immutable') === '0' && !is_trash){
+        menu_items.push('-')
+    }
+    // -------------------------------------------
+    // Create Shortcut
+    // -------------------------------------------
+    if(!is_trashed && window.feature_flags.create_shortcut){
+        menu_items.push({
+            html: is_shared_with_me ? i18n('create_desktop_shortcut') : i18n('create_shortcut'),
+            onClick: async function(){
+                let base_dir = path.dirname($(el_item).attr('data-path'));
+                // Trash on Desktop is a special case
+                if($(el_item).attr('data-path') && $(el_item).closest('.item-container').attr('data-path') === window.desktop_path){
+                    base_dir = window.desktop_path;
+                }
+
+                if ( is_shared_with_me ) base_dir = window.desktop_path;
+
+                window.create_shortcut(
+                    path.basename($(el_item).attr('data-path')), 
+                    options.is_dir, 
+                    base_dir, 
+                    options.appendTo, 
+                    options.shortcut_to === '' ? options.uid : options.shortcut_to,
+                    options.shortcut_to_path === '' ? options.path : options.shortcut_to_path,
+                );
+            }
+        });
+    }
+    // -------------------------------------------
+    // Delete
+    // -------------------------------------------
+    if($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_shared_with_me){
+        menu_items.push({
+            html: i18n('delete'),
+            onClick: async function(){
+                window.move_items([el_item], window.trash_path);
+            }
+        });
+    }
+    // -------------------------------------------
+    // Delete Permanently
+    // -------------------------------------------
+    if(is_trashed){
+        menu_items.push({
+            html: i18n('delete_permanently'),
+            onClick: async function(){
+                const alert_resp = await UIAlert({
+                    message: i18n('confirm_delete_single_item'),
+                    buttons:[
+                        {
+                            label: i18n('delete'),
+                            type: 'primary',
+                        },
+                        {
+                            label: i18n('cancel')
+                        },
+                    ]
+                })
+
+                if((alert_resp) === 'Delete'){
+                    await window.delete_item(el_item);
+                    // check if trash is empty
+                    const trash = await puter.fs.stat(window.trash_path);
+                    // update other clients
+                    if(window.socket){
+                        window.socket.emit('trash.is_empty', {is_empty: trash.is_empty});
+                    }
+                    // update this client
+                    if(trash.is_empty){
+                        $(`.item[data-path="${html_encode(window.trash_path)}" i], .item[data-shortcut_to_path="${html_encode(window.trash_path)}" i]`).find('.item-icon > img').attr('src', window.icons['trash.svg']);
+                        $(`.window[data-path="${window.trash_path}"]`).find('.window-head-icon').attr('src', window.icons['trash.svg']);
+                    }            
+                }
+            }
+        });
+    }
+    // -------------------------------------------
+    // Rename
+    // -------------------------------------------
+    if($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_trash){
+        menu_items.push({
+            html: i18n('rename'),
+            onClick: function(){
+                window.activate_item_name_editor(el_item)
+            }
+        });
+    }
+    // -------------------------------------------
+    // -
+    // -------------------------------------------
+    menu_items.push('-');
+    // -------------------------------------------
+    // Properties
+    // -------------------------------------------
+    menu_items.push({
+        html: i18n('properties'),
+        onClick: function(){
+            let window_height = 500;
+            let window_width = 450;
+
+            let left = $(el_item).position().left + $(el_item).width();
+            left = left > (window.innerWidth - window_width)? (window.innerWidth - window_width) : left;
+
+            let top = $(el_item).position().top + $(el_item).height();
+            top = top > (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height))? (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height)) : top;
+
+            UIWindowItemProperties(
+                $(el_item).attr('data-name'), 
+                $(el_item).attr('data-path'), 
+                $(el_item).attr('data-uid'),
+                left,
+                top,
+                window_width,
+                window_height,
+            );
+        }
+    });
+};