diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index 1a2da3a0..182abb76 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -26,14 +26,13 @@ import UIWindow from './UIWindow.js' import UIWindowSaveAccount from './UIWindowSaveAccount.js'; import UIWindowDesktopBGSettings from "./UIWindowDesktopBGSettings.js" import UIWindowMyWebsites from "./UIWindowMyWebsites.js" -import UIWindowChangePassword from "./UIWindowChangePassword.js" -import UIWindowChangeUsername from "./UIWindowChangeUsername.js" import UIWindowFeedback from "./UIWindowFeedback.js" import UIWindowLogin from "./UIWindowLogin.js" import UIWindowQR from "./UIWindowQR.js" import UIWindowRefer from "./UIWindowRefer.js" import UITaskbar from "./UITaskbar.js" import new_context_menu_item from "../helpers/new_context_menu_item.js" +import refresh_item_container from "../helpers/refresh_item_container.js" import changeLanguage from "../i18n/i18nChangeLanguage.js" import UIWindowSettings from "./Settings/UIWindowSettings.js" diff --git a/src/UI/UIWindow.js b/src/UI/UIWindow.js index 48d4a68c..0fc8a887 100644 --- a/src/UI/UIWindow.js +++ b/src/UI/UIWindow.js @@ -25,6 +25,7 @@ import UIWindowLogin from './UIWindowLogin.js'; import UIWindowPublishWebsite from './UIWindowPublishWebsite.js'; import UIWindowItemProperties from './UIWindowItemProperties.js'; import new_context_menu_item from '../helpers/new_context_menu_item.js'; +import refresh_item_container from '../helpers/refresh_item_container.js'; const el_body = document.getElementsByTagName('body')[0]; diff --git a/src/helpers.js b/src/helpers.js index 83310b69..ebbe9f1c 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1165,238 +1165,6 @@ window.launch_download_from_url = async function(){ } } -window.refresh_item_container = function(el_item_container, options){ - options = options || {}; - - let container_path = $(el_item_container).attr('data-path'); - let el_window = $(el_item_container).closest('.window'); - let el_window_head_icon = $(el_window).find('.window-head-icon'); - const loading_spinner = $(el_item_container).find('.explorer-loading-spinner'); - const error_message = $(el_item_container).find('.explorer-error-message'); - const empty_message = $(el_item_container).find('.explorer-empty-message'); - - if(options.fadeInItems) - $(el_item_container).css('opacity', '0') - - // Hide the 'This folder is empty' message to avoid the flickering effect - // if the folder is not empty. - $(el_item_container).find('.explorer-empty-message').hide(); - - // Hide the loading spinner to avoid the flickering effect if the folder - // is already loaded. - $(loading_spinner).hide(); - - // Hide the error message in case it's visible - $(error_message).hide(); - - // current timestamp in milliseconds - let start_ts = new Date().getTime(); - - // A timeout that will show the loading spinner if the folder is not loaded - // after 1000ms - let loading_timeout = setTimeout(function(){ - // make sure the same folder is still loading - if($(loading_spinner).closest('.item-container').attr('data-path') !== container_path) - return; - - // show the loading spinner - $(loading_spinner).show(); - setTimeout(function(){ - $(loading_spinner).find('.explorer-loading-spinner-msg').html('Taking a little longer than usual. Please wait...'); - }, 3000) - }, 1000); - - // -------------------------------------------------------- - // Folder's configs and properties - // -------------------------------------------------------- - puter.fs.stat(container_path, function(fsentry){ - if(el_window){ - $(el_window).attr('data-uid', fsentry.id); - $(el_window).attr('data-sort_by', fsentry.sort_by ?? 'name'); - $(el_window).attr('data-sort_order', fsentry.sort_order ?? 'asc'); - $(el_window).attr('data-layout', fsentry.layout ?? 'icons'); - // data-name - $(el_window).attr('data-name', html_encode(fsentry.name)); - // data-path - $(el_window).attr('data-path', html_encode(container_path)); - $(el_window).find('.window-navbar-path-input').val(container_path); - $(el_window).find('.window-navbar-path-input').attr('data-path', container_path); - } - $(el_item_container).attr('data-sort_by', fsentry.sort_by ?? 'name'); - $(el_item_container).attr('data-sort_order', fsentry.sort_order ?? 'asc'); - // update layout - if(el_window && el_window.length > 0) - update_window_layout(el_window, fsentry.layout); - // - if(fsentry.layout === 'details'){ - update_details_layout_sort_visuals(el_window, fsentry.sort_by, fsentry.sort_order); - } - }); - - // is_directoryPicker - let is_directoryPicker = $(el_window).attr('data-is_directoryPicker'); - is_directoryPicker = (is_directoryPicker === 'true' || is_directoryPicker === '1') ? true : false; - - // allowed_file_types - let allowed_file_types = $(el_window).attr('data-allowed_file_types'); - - // is_directoryPicker - let is_openFileDialog = $(el_window).attr('data-is_openFileDialog'); - is_openFileDialog = (is_openFileDialog === 'true' || is_openFileDialog === '1') ? true : false; - - // remove all existing items - $(el_item_container).find('.item').removeItems() - - // get items - puter.fs.readdir(container_path).then((fsentries)=>{ - // Check if the same folder is still loading since el_item_container's - // data-path might have changed by other operations while waiting for the response to this `readdir`. - if($(el_item_container).attr('data-path') !== container_path) - return; - - setTimeout(async function(){ - // clear loading timeout - clearTimeout(loading_timeout); - - // hide loading spinner - $(loading_spinner).hide(); - - // if no items, show empty folder message - if(fsentries.length === 0){ - $(el_item_container).find('.explorer-empty-message').show(); - } - - // trash icon - if(container_path === trash_path && el_window_head_icon){ - if(fsentries.length > 0){ - $(el_window_head_icon).attr('src', window.icons['trash-full.svg']); - }else{ - $(el_window_head_icon).attr('src', window.icons['trash.svg']); - } - } - - // add each item to window - for (let index = 0; index < fsentries.length; index++) { - const fsentry = fsentries[index]; - let is_disabled = false; - - // disable files if this is a showDirectoryPicker() window - if(is_directoryPicker && !fsentry.is_dir) - is_disabled = true; - - // if this item is not allowed because of filetype restrictions, disable it - if(!window.check_fsentry_against_allowed_file_types_string(fsentry, allowed_file_types)) - is_disabled = true; - - // set visibility based on user preferences and whether file is hidden by default - const is_hidden_file = fsentry.name.startsWith('.'); - let visible; - if (!is_hidden_file){ - visible = 'visible'; - }else if (window.user_preferences.show_hidden_files) { - visible = 'revealed'; - }else{ - visible = 'hidden'; - } - - // metadata - let metadata; - if(fsentry.metadata !== ''){ - try{ - metadata = JSON.parse(fsentry.metadata); - } - catch(e){ - } - } - - const item_path = fsentry.path ?? path.join($(el_window).attr('data-path'), fsentry.name); - // render any item but Trash/AppData - if(item_path !== trash_path && item_path !== appdata_path){ - // if this is trash, get original name from item metadata - fsentry.name = (metadata && metadata.original_name !== undefined) ? metadata.original_name : fsentry.name; - const position = desktop_item_positions[fsentry.uid] ?? undefined; - UIItem({ - appendTo: el_item_container, - uid: fsentry.uid, - immutable: fsentry.immutable, - associated_app_name: fsentry.associated_app?.name, - path: item_path, - icon: await item_icon(fsentry), - name: (metadata && metadata.original_name !== undefined) ? metadata.original_name : fsentry.name, - is_dir: fsentry.is_dir, - multiselectable: !is_openFileDialog, - has_website: fsentry.has_website, - is_shared: fsentry.is_shared, - metadata: fsentry.metadata, - is_shortcut: fsentry.is_shortcut, - shortcut_to: fsentry.shortcut_to, - shortcut_to_path: fsentry.shortcut_to_path, - size: fsentry.size, - type: fsentry.type, - modified: fsentry.modified, - suggested_apps: fsentry.suggested_apps, - disabled: is_disabled, - visible: visible, - position: position, - }); - } - } - - // if this is desktop, add Trash - if($(el_item_container).hasClass('desktop')){ - try{ - const trash = await puter.fs.stat(window.trash_path); - UIItem({ - appendTo: el_item_container, - uid: trash.id, - immutable: trash.immutable, - path: trash_path, - icon: {image: (trash.is_empty ? window.icons['trash.svg'] : window.icons['trash-full.svg']), type: 'icon'}, - name: trash.name, - is_dir: trash.is_dir, - sort_by: trash.sort_by, - type: trash.type, - is_trash: true, - sortable: false, - }); - sort_items(el_item_container, $(el_item_container).attr('data-sort_by'), $(el_item_container).attr('data-sort_order')); - }catch(e){ - - } - } - // sort items - sort_items( - el_item_container, - $(el_item_container).attr('data-sort_by'), - $(el_item_container).attr('data-sort_order') - ); - - if(options.fadeInItems) - $(el_item_container).animate({'opacity': '1'}); - - // update footer item count if this is an explorer window - if(el_window) - update_explorer_footer_item_count(el_window); - }, - // This makes sure the loading spinner shows up if the request takes longer than 1 second - // and stay there for at least 1 second since the flickering is annoying - (Date.now() - start_ts) > 1000 ? 1000 : 1) - }).catch(e => { - // clear loading timeout - clearTimeout(loading_timeout); - - // hide other messages/indicators - $(loading_spinner).hide(); - $(empty_message).hide(); - - // UIAlert('Failed to load directory' + (e && e.message ? ': ' + e.message : '')); - - // show error message - $(error_message).html('Failed to load directory' + (e && e.message ? ': ' + e.message : '')); - $(error_message).show(); - }); -} - window.onpopstate = (event) => { if(event.state !== null && event.state.window_id !== null){ $(`.window[data-id="${event.state.window_id}"]`).focusWindow(); diff --git a/src/helpers/refresh_item_container.js b/src/helpers/refresh_item_container.js new file mode 100644 index 00000000..7a083ded --- /dev/null +++ b/src/helpers/refresh_item_container.js @@ -0,0 +1,252 @@ +/** + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import UIItem from '../UI/UIItem.js'; + +const refresh_item_container = function(el_item_container, options){ + options = options || {}; + + let container_path = $(el_item_container).attr('data-path'); + let el_window = $(el_item_container).closest('.window'); + let el_window_head_icon = $(el_window).find('.window-head-icon'); + const loading_spinner = $(el_item_container).find('.explorer-loading-spinner'); + const error_message = $(el_item_container).find('.explorer-error-message'); + const empty_message = $(el_item_container).find('.explorer-empty-message'); + + if(options.fadeInItems) + $(el_item_container).css('opacity', '0') + + // Hide the 'This folder is empty' message to avoid the flickering effect + // if the folder is not empty. + $(el_item_container).find('.explorer-empty-message').hide(); + + // Hide the loading spinner to avoid the flickering effect if the folder + // is already loaded. + $(loading_spinner).hide(); + + // Hide the error message in case it's visible + $(error_message).hide(); + + // current timestamp in milliseconds + let start_ts = new Date().getTime(); + + // A timeout that will show the loading spinner if the folder is not loaded + // after 1000ms + let loading_timeout = setTimeout(function(){ + // make sure the same folder is still loading + if($(loading_spinner).closest('.item-container').attr('data-path') !== container_path) + return; + + // show the loading spinner + $(loading_spinner).show(); + setTimeout(function(){ + $(loading_spinner).find('.explorer-loading-spinner-msg').html('Taking a little longer than usual. Please wait...'); + }, 3000) + }, 1000); + + // -------------------------------------------------------- + // Folder's configs and properties + // -------------------------------------------------------- + puter.fs.stat(container_path, function(fsentry){ + if(el_window){ + $(el_window).attr('data-uid', fsentry.id); + $(el_window).attr('data-sort_by', fsentry.sort_by ?? 'name'); + $(el_window).attr('data-sort_order', fsentry.sort_order ?? 'asc'); + $(el_window).attr('data-layout', fsentry.layout ?? 'icons'); + // data-name + $(el_window).attr('data-name', html_encode(fsentry.name)); + // data-path + $(el_window).attr('data-path', html_encode(container_path)); + $(el_window).find('.window-navbar-path-input').val(container_path); + $(el_window).find('.window-navbar-path-input').attr('data-path', container_path); + } + $(el_item_container).attr('data-sort_by', fsentry.sort_by ?? 'name'); + $(el_item_container).attr('data-sort_order', fsentry.sort_order ?? 'asc'); + // update layout + if(el_window && el_window.length > 0) + update_window_layout(el_window, fsentry.layout); + // + if(fsentry.layout === 'details'){ + update_details_layout_sort_visuals(el_window, fsentry.sort_by, fsentry.sort_order); + } + }); + + // is_directoryPicker + let is_directoryPicker = $(el_window).attr('data-is_directoryPicker'); + is_directoryPicker = (is_directoryPicker === 'true' || is_directoryPicker === '1') ? true : false; + + // allowed_file_types + let allowed_file_types = $(el_window).attr('data-allowed_file_types'); + + // is_directoryPicker + let is_openFileDialog = $(el_window).attr('data-is_openFileDialog'); + is_openFileDialog = (is_openFileDialog === 'true' || is_openFileDialog === '1') ? true : false; + + // remove all existing items + $(el_item_container).find('.item').removeItems() + + // get items + puter.fs.readdir(container_path).then((fsentries)=>{ + // Check if the same folder is still loading since el_item_container's + // data-path might have changed by other operations while waiting for the response to this `readdir`. + if($(el_item_container).attr('data-path') !== container_path) + return; + + setTimeout(async function(){ + // clear loading timeout + clearTimeout(loading_timeout); + + // hide loading spinner + $(loading_spinner).hide(); + + // if no items, show empty folder message + if(fsentries.length === 0){ + $(el_item_container).find('.explorer-empty-message').show(); + } + + // trash icon + if(container_path === trash_path && el_window_head_icon){ + if(fsentries.length > 0){ + $(el_window_head_icon).attr('src', window.icons['trash-full.svg']); + }else{ + $(el_window_head_icon).attr('src', window.icons['trash.svg']); + } + } + + // add each item to window + for (let index = 0; index < fsentries.length; index++) { + const fsentry = fsentries[index]; + let is_disabled = false; + + // disable files if this is a showDirectoryPicker() window + if(is_directoryPicker && !fsentry.is_dir) + is_disabled = true; + + // if this item is not allowed because of filetype restrictions, disable it + if(!window.check_fsentry_against_allowed_file_types_string(fsentry, allowed_file_types)) + is_disabled = true; + + // set visibility based on user preferences and whether file is hidden by default + const is_hidden_file = fsentry.name.startsWith('.'); + let visible; + if (!is_hidden_file){ + visible = 'visible'; + }else if (window.user_preferences.show_hidden_files) { + visible = 'revealed'; + }else{ + visible = 'hidden'; + } + + // metadata + let metadata; + if(fsentry.metadata !== ''){ + try{ + metadata = JSON.parse(fsentry.metadata); + } + catch(e){ + } + } + + const item_path = fsentry.path ?? path.join($(el_window).attr('data-path'), fsentry.name); + // render any item but Trash/AppData + if(item_path !== trash_path && item_path !== appdata_path){ + // if this is trash, get original name from item metadata + fsentry.name = (metadata && metadata.original_name !== undefined) ? metadata.original_name : fsentry.name; + const position = desktop_item_positions[fsentry.uid] ?? undefined; + UIItem({ + appendTo: el_item_container, + uid: fsentry.uid, + immutable: fsentry.immutable, + associated_app_name: fsentry.associated_app?.name, + path: item_path, + icon: await item_icon(fsentry), + name: (metadata && metadata.original_name !== undefined) ? metadata.original_name : fsentry.name, + is_dir: fsentry.is_dir, + multiselectable: !is_openFileDialog, + has_website: fsentry.has_website, + is_shared: fsentry.is_shared, + metadata: fsentry.metadata, + is_shortcut: fsentry.is_shortcut, + shortcut_to: fsentry.shortcut_to, + shortcut_to_path: fsentry.shortcut_to_path, + size: fsentry.size, + type: fsentry.type, + modified: fsentry.modified, + suggested_apps: fsentry.suggested_apps, + disabled: is_disabled, + visible: visible, + position: position, + }); + } + } + + // if this is desktop, add Trash + if($(el_item_container).hasClass('desktop')){ + try{ + const trash = await puter.fs.stat(window.trash_path); + UIItem({ + appendTo: el_item_container, + uid: trash.id, + immutable: trash.immutable, + path: trash_path, + icon: {image: (trash.is_empty ? window.icons['trash.svg'] : window.icons['trash-full.svg']), type: 'icon'}, + name: trash.name, + is_dir: trash.is_dir, + sort_by: trash.sort_by, + type: trash.type, + is_trash: true, + sortable: false, + }); + sort_items(el_item_container, $(el_item_container).attr('data-sort_by'), $(el_item_container).attr('data-sort_order')); + }catch(e){ + + } + } + // sort items + sort_items( + el_item_container, + $(el_item_container).attr('data-sort_by'), + $(el_item_container).attr('data-sort_order') + ); + + if(options.fadeInItems) + $(el_item_container).animate({'opacity': '1'}); + + // update footer item count if this is an explorer window + if(el_window) + update_explorer_footer_item_count(el_window); + }, + // This makes sure the loading spinner shows up if the request takes longer than 1 second + // and stay there for at least 1 second since the flickering is annoying + (Date.now() - start_ts) > 1000 ? 1000 : 1) + }).catch(e => { + // clear loading timeout + clearTimeout(loading_timeout); + + // hide other messages/indicators + $(loading_spinner).hide(); + $(empty_message).hide(); + + // show error message + $(error_message).html('Failed to load directory' + (e && e.message ? ': ' + e.message : '')); + $(error_message).show(); + }); +} + +export default refresh_item_container; \ No newline at end of file