/** * 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 path from "../lib/path.js" import UIWindowClaimReferral from "./UIWindowClaimReferral.js" import UIContextMenu from './UIContextMenu.js' import UIItem from './UIItem.js' import UIAlert from './UIAlert.js' 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" async function UIDesktop(options){ let h = ''; // connect socket. window.socket = io(gui_origin + '/', { query: { auth_token: auth_token } }); window.socket.on('error', (error) => { console.error('GUI Socket Error:', error); }); window.socket.on('connect', function(){ console.log('GUI Socket: Connected', window.socket.id); }); window.socket.on('reconnect', function(){ console.log('GUI Socket: Reconnected', window.socket.id); }); window.socket.on('disconnect', () => { console.log('GUI Socket: Disconnected'); }); window.socket.on('reconnect', (attempt) => { console.log('GUI Socket: Reconnection', attempt); }); window.socket.on('reconnect_attempt', (attempt) => { console.log('GUI Socket: Reconnection Attemps', attempt); }); window.socket.on('reconnect_error', (error) => { console.log('GUI Socket: Reconnection Error', error); }); window.socket.on('reconnect_failed', () => { console.log('GUI Socket: Reconnection Failed'); }); window.socket.on('error', (error) => { console.error('GUI Socket Error:', error); }); socket.on('upload.progress', (msg) => { if(window.progress_tracker[msg.operation_id]){ window.progress_tracker[msg.operation_id].cloud_uploaded += msg.loaded_diff if(window.progress_tracker[msg.operation_id][msg.item_upload_id]){ window.progress_tracker[msg.operation_id][msg.item_upload_id].cloud_uploaded = msg.loaded; } } }); socket.on('download.progress', (msg) => { if(window.progress_tracker[msg.operation_id]){ if(window.progress_tracker[msg.operation_id][msg.item_upload_id]){ window.progress_tracker[msg.operation_id][msg.item_upload_id].downloaded = msg.loaded; window.progress_tracker[msg.operation_id][msg.item_upload_id].total = msg.total; } } }); socket.on('trash.is_empty', async (msg) => { $(`.item[data-path="${html_encode(trash_path)}" i]`).find('.item-icon > img').attr('src', msg.is_empty ? window.icons['trash.svg'] : window.icons['trash-full.svg']); $(`.window[data-path="${html_encode(trash_path)}" i]`).find('.window-head-icon').attr('src', msg.is_empty ? window.icons['trash.svg'] : window.icons['trash-full.svg']); // empty trash windows if needed if(msg.is_empty) $(`.window[data-path="${html_encode(trash_path)}" i]`).find('.item-container').empty(); }) socket.on('app.opened', async (app) => { // don't update if this is the original client that initiated the action if(app.original_client_socket_id === window.socket.id) return; // add the app to the beginning of the array launch_apps.recent.unshift(app); // dedupe the array by uuid, uid, and id launch_apps.recent = _.uniqBy(launch_apps.recent, 'name'); // limit to 5 launch_apps.recent = launch_apps.recent.slice(0, window.launch_recent_apps_count); }) socket.on('item.removed', async (item) => { // don't update if this is the original client that initiated the action if(item.original_client_socket_id === window.socket.id) return; // don't remove items if this was a descendants_only operation if(item.descendants_only) return; // hide all UIItems with matching uids $(`.item[data-path='${item.path}']`).fadeOut(150, function(){ // close all windows with matching uids // $('.window-' + item.uid).close(); // close all windows that belong to a descendant of this item // todo this has to be case-insensitive but the `i` selector doesn't work on ^= $(`.window[data-path^="${item.path}/"]`).close(); }); }) socket.on('item.updated', async (item) => { // Don't update if this is the original client that initiated the action if(item.original_client_socket_id === window.socket.id) return; // Update matching items // set new item name $(`.item[data-uid='${html_encode(item.uid)}'] .item-name`).html(html_encode(truncate_filename(item.name, TRUNCATE_LENGTH)).replaceAll(' ', ' ')); // Set new icon const new_icon = (item.is_dir ? window.icons['folder.svg'] : (await item_icon(item)).image); $(`.item[data-uid='${item.uid}']`).find('.item-icon-thumb').attr('src', new_icon); $(`.item[data-uid='${item.uid}']`).find('.item-icon-icon').attr('src', new_icon); // Set new data-name $(`.item[data-uid='${item.uid}']`).attr('data-name', html_encode(item.name)); $(`.window-${item.uid}`).attr('data-name', html_encode(item.name)); // Set new title attribute $(`.item[data-uid='${item.uid}']`).attr('title', html_encode(item.name)); $(`.window-${options.uid}`).attr('title', html_encode(item.name)); // Set new value for item-name-editor $(`.item[data-uid='${item.uid}'] .item-name-editor`).val(html_encode(item.name)); $(`.item[data-uid='${item.uid}'] .item-name`).attr('title', html_encode(item.name)); // Set new data-path const new_path = item.path; $(`.item[data-uid='${item.uid}']`).attr('data-path', new_path); $(`.window-${item.uid}`).attr('data-path', new_path); // Update all elements that have matching paths $(`[data-path="${html_encode(item.old_path)}" i]`).each(function(){ $(this).attr('data-path', new_path) if($(this).hasClass('window-navbar-path-dirname')) $(this).text(item.name); }); // Update all elements whose paths start with old_path $(`[data-path^="${html_encode(item.old_path) + '/'}"]`).each(function(){ const new_el_path = _.replace($(this).attr('data-path'), item.old_path + '/', new_path+'/'); $(this).attr('data-path', new_el_path); }); // Update all exact-matching windows $(`.window-${item.uid}`).each(function(){ update_window_path(this, new_path); }) // Set new name for matching open windows $(`.window-${item.uid} .window-head-title`).text(item.name); // Re-sort all matching item containers $(`.item[data-uid='${item.uid}']`).parent('.item-container').each(function(){ sort_items(this, $(this).closest('.item-container').attr('data-sort_by'), $(this).closest('.item-container').attr('data-sort_order')); }) }) socket.on('item.moved', async (resp) => { let fsentry = resp; // Notify all apps that are watching this item sendItemChangeEventToWatchingApps(fsentry.uid, { event: 'moved', uid: fsentry.uid, name: fsentry.name, }) // don't update if this is the original client that initiated the action if(resp.original_client_socket_id === window.socket.id) return; let dest_path = path.dirname(fsentry.path); let metadata = fsentry.metadata; // path must use the real name from DB fsentry.path = fsentry.path; // update all shortcut_to_path $(`.item[data-shortcut_to_path="${html_encode(resp.old_path)}" i]`).attr(`data-shortcut_to_path`, html_encode(fsentry.path)); // remove all items with matching uids $(`.item[data-uid='${fsentry.uid}']`).fadeOut(150, function(){ // find all parent windows that contain this item let parent_windows = $(`.item[data-uid='${fsentry.uid}']`).closest('.window'); // remove this item $(this).removeItems(); // update parent windows' item counts $(parent_windows).each(function(index){ update_explorer_footer_item_count(this); update_explorer_footer_selected_items_count(this) }); }) // if trashing, close windows of trashed items and its descendants if(dest_path === trash_path){ $(`.window[data-path="${html_encode(resp.old_path)}" i]`).close(); // todo this has to be case-insensitive but the `i` selector doesn't work on ^= $(`.window[data-path^="${html_encode(resp.old_path)}/"]`).close(); } // update all paths of its and its descendants' open windows else{ // todo this has to be case-insensitive but the `i` selector doesn't work on ^= $(`.window[data-path^="${html_encode(resp.old_path)}/"], .window[data-path="${html_encode(resp.old_path)}" i]`).each(function(){ update_window_path(this, $(this).attr('data-path').replace(resp.old_path, fsentry.path)); }) } if(dest_path === trash_path){ $(`.item[data-uid="${fsentry.uid}"]`).find('.item-is-shared').fadeOut(300); // if trashing dir... if(fsentry.is_dir){ // remove website badge $(`.mywebsites-dir-path[data-uuid="${fsentry.uid}"]`).remove(); // remove the website badge from all instances of the dir $(`.item[data-uid="${fsentry.uid}"]`).find('.item-has-website-badge').fadeOut(300); // remove File Rrequest Token // todo, some client-side check to see if this dir has an FR associated with it before sending a whole ajax req } } // if replacing an existing item, remove the old item that was just replaced if(fsentry.overwritten_uid !== undefined) $(`.item[data-uid=${fsentry.overwritten_uid}]`).removeItems(); // if this is trash, get original name from item metadata fsentry.name = (metadata && metadata.original_name) ? metadata.original_name : fsentry.name; // create new item on matching containers UIItem({ appendTo: $(`.item-container[data-path='${html_encode(dest_path)}' i]`), immutable: fsentry.immutable, uid: fsentry.uid, path: fsentry.path, icon: await item_icon(fsentry), name: (dest_path === trash_path) ? metadata.original_name : fsentry.name, is_dir: fsentry.is_dir, size: fsentry.size, type: fsentry.type, modified: fsentry.modified, is_selected: false, is_shared: (dest_path === trash_path) ? false : fsentry.is_shared, is_shortcut: fsentry.is_shortcut, shortcut_to: fsentry.shortcut_to, shortcut_to_path: fsentry.shortcut_to_path, // has_website: $(el_item).attr('data-has_website') === '1', metadata: JSON.stringify(fsentry.metadata) ?? '', }); if(fsentry.parent_dirs_created && fsentry.parent_dirs_created.length > 0){ // this operation may have created some missing directories, // see if any of the directories in the path of this file is new AND // if these new path have any open parents that need to be updated fsentry.parent_dirs_created.forEach(async dir => { let item_container = $(`.item-container[data-path='${html_encode(path.dirname(dir.path))}' i]`); if(item_container.length > 0 && $(`.item[data-path="${html_encode(dir.path)}" i]`).length === 0){ UIItem({ appendTo: item_container, immutable: false, uid: dir.uid, path: dir.path, icon: await item_icon(dir), name: dir.name, size: dir.size, type: dir.type, modified: dir.modified, is_dir: true, is_selected: false, is_shared: dir.is_shared, has_website: false, }); } sort_items(item_container, $(item_container).attr('data-sort_by'), $(item_container).attr('data-sort_order')); }); } //sort each container $(`.item-container[data-path='${html_encode(dest_path)}' i]`).each(function(){ sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order')) }) }); socket.on('user.email_confirmed', (msg) => { // don't update if this is the original client that initiated the action if(msg.original_client_socket_id === window.socket.id) return; refresh_user_data(window.auth_token); }); socket.on('item.renamed', async (item) => { // Notify all apps that are watching this item sendItemChangeEventToWatchingApps(item.uid, { event: 'rename', uid: item.uid, // path: item.path, new_name: item.name, // old_path: item.old_path, }) // Don't update if this is the original client that initiated the action if(item.original_client_socket_id === window.socket.id) return; // Update matching items // Set new item name $(`.item[data-uid='${html_encode(item.uid)}'] .item-name`).html(html_encode(truncate_filename(item.name, TRUNCATE_LENGTH)).replaceAll(' ', ' ')); // Set new icon const new_icon = (item.is_dir ? window.icons['folder.svg'] : (await item_icon(item)).image); $(`.item[data-uid='${item.uid}']`).find('.item-icon-icon').attr('src', new_icon); // Set new data-name $(`.item[data-uid='${item.uid}']`).attr('data-name', html_encode(item.name)); $(`.window-${item.uid}`).attr('data-name', html_encode(item.name)); // Set new title attribute $(`.item[data-uid='${item.uid}']`).attr('title', html_encode(item.name)); $(`.window-${options.uid}`).attr('title', html_encode(item.name)); // Set new value for item-name-editor $(`.item[data-uid='${item.uid}'] .item-name-editor`).val(html_encode(item.name)); $(`.item[data-uid='${item.uid}'] .item-name`).attr('title', html_encode(item.name)); // Set new data-path const new_path = item.path; $(`.item[data-uid='${item.uid}']`).attr('data-path', new_path); $(`.window-${item.uid}`).attr('data-path', new_path); // Update all elements that have matching paths $(`[data-path="${html_encode(item.old_path)}" i]`).each(function(){ $(this).attr('data-path', new_path) if($(this).hasClass('window-navbar-path-dirname')) $(this).text(item.name); }); // Update all elements whose paths start with old_path $(`[data-path^="${html_encode(item.old_path) + '/'}"]`).each(function(){ const new_el_path = _.replace($(this).attr('data-path'), item.old_path + '/', new_path+'/'); $(this).attr('data-path', new_el_path); }); // Update all exact-matching windows $(`.window-${item.uid}`).each(function(){ update_window_path(this, new_path); }) // Set new name for matching open windows $(`.window-${item.uid} .window-head-title`).text(item.name); // Re-sort all matching item containers $(`.item[data-uid='${item.uid}']`).parent('.item-container').each(function(){ sort_items(this, $(this).closest('.item-container').attr('data-sort_by'), $(this).closest('.item-container').attr('data-sort_order')); }) }); socket.on('item.added', async (item) => { // if item is empty, don't proceed if(_.isEmpty(item)) return; // Notify all apps that are watching this item sendItemChangeEventToWatchingApps(item.uid, { event: 'write', uid: item.uid, // path: item.path, new_size: item.size, modified: item.modified, // old_path: item.old_path, }); // Don't update if this is the original client that initiated the action if(item.original_client_socket_id === window.socket.id) return; // Update replaced items with matching uids if(item.overwritten_uid){ $(`.item[data-uid='${item.overwritten_uid}']`).attr({ 'data-immutable': item.immutable, 'data-path': item.path, 'data-name': item.name, 'data-size': item.size, 'data-modified': item.modified, 'data-is_shared': item.is_shared, 'data-type': item.type, }) // set new icon const new_icon = (item.is_dir ? window.icons['folder.svg'] : (await item_icon(item)).image); $(`.item[data-uid="${item.overwritten_uid}"]`).find('.item-icon > img').attr('src', new_icon); //sort each window $(`.item-container[data-path='${html_encode(item.dirpath)}' i]`).each(function(){ sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order')) }) } else{ UIItem({ appendTo: $(`.item-container[data-path='${html_encode(item.dirpath)}' i]`), uid: item.uid, immutable: item.immutable, associated_app_name: item.associated_app?.name, path: item.path, icon: await item_icon(item), name: item.name, size: item.size, type: item.type, modified: item.modified, is_dir: item.is_dir, is_shared: item.is_shared, is_shortcut: item.is_shortcut, associated_app_name: item.associated_app?.name, shortcut_to: item.shortcut_to, shortcut_to_path: item.shortcut_to_path, }); //sort each window $(`.item-container[data-path='${html_encode(item.dirpath)}' i]`).each(function(){ sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order')) }) } }); // Hidden file dialog h += `
`; h += `
`; // Desktop // If desktop is not in fullpage/embedded mode, we hide it until files and directories are loaded and then fade in the UI // This gives a calm and smooth experience for the user h += `
`; h += `
`; // Get window sidebar width getItem({ key: "window_sidebar_width", success: async function(res){ let value = parseInt(res.value); // if value is a valid number if(!isNaN(value) && value > 0){ window.window_sidebar_width = value; } } }) // Remove `?ref=...` from navbar URL if(url_query_params.has('ref')){ window.history.pushState(null, document.title, '/'); } // Append to $('body').append(h); // Set desktop height based on taskbar height $('.desktop').css('height', `calc(100vh - ${window.taskbar_height + window.toolbar_height}px)`) // --------------------------------------------------------------- // Taskbar // --------------------------------------------------------------- UITaskbar(); const el_desktop = document.querySelector('.desktop'); window.active_element = el_desktop; window.active_item_container = el_desktop; // -------------------------------------------------------- // Dragster // Allow dragging of local files onto desktop. // -------------------------------------------------------- $(el_desktop).dragster({ enter: function (dragsterEvent, event) { $('.context-menu').remove(); }, leave: function (dragsterEvent, event) { }, drop: async function (dragsterEvent, event) { const e = event.originalEvent; // no drop on item if($(event.target).hasClass('item') || $(event.target).parent('.item').length > 0) return false; // recursively create directories and upload files if(e.dataTransfer?.items?.length>0){ upload_items(e.dataTransfer.items, desktop_path); } e.stopPropagation(); e.preventDefault(); return false; } }); // -------------------------------------------------------- // Droppable // -------------------------------------------------------- $(el_desktop).droppable({ accept: '.item', tolerance: "intersect", drop: function( event, ui ) { // Check if item was actually dropped on desktop and not a window if(mouseover_window !== undefined) return; // Can't drop anything but UIItems on desktop if(!$(ui.draggable).hasClass('item')) return; // Don't move an item to its current directory if( path.dirname($(ui.draggable).attr('data-path')) === desktop_path && !event.ctrlKey) return; // If ctrl is pressed and source is Trashed, cancel whole operation if(event.ctrlKey && path.dirname($(ui.draggable).attr('data-path')) === window.trash_path) return; // Unselect previously selected items $(el_desktop).children('.item-selected').removeClass('item-selected'); const items_to_move = [] // first item items_to_move.push(ui.draggable); // all subsequent items const cloned_items = document.getElementsByClassName('item-selected-clone'); for(let i =0; i 0 ? false : true, onClick: function(){ if(clipboard_op === 'copy') copy_clipboard_items(desktop_path, el_desktop); else if(clipboard_op === 'move') move_clipboard_items(el_desktop) } }, // ------------------------------------------- // Upload Here // ------------------------------------------- { html: "Upload Here", onClick: function(){ init_upload_using_dialog(el_desktop); } }, // ------------------------------------------- // Request Files // ------------------------------------------- // { // html: "Request Files", // onClick: function(){ // UIWindowRequestFiles({dir_path: desktop_path}) // } // }, // ------------------------------------------- // - // ------------------------------------------- '-', // ------------------------------------------- // Change Desktop Background… // ------------------------------------------- { html: "Change Desktop Background…", onClick: function(){ UIWindowDesktopBGSettings(); } }, ] }); } }); //------------------------------------------- // Desktop Files/Folders // we don't need to get the desktop items if we're in embedded or fullpage mode // because the items aren't visible anyway and we don't need to waste bandwidth/server resources //------------------------------------------- if(!is_embedded && !window.is_fullpage_mode){ refresh_item_container(el_desktop, {fadeInItems: true}) window.launch_download_from_url(); } // ------------------------------------------- // Selectable // Only for desktop // ------------------------------------------- if(!isMobile.phone && !isMobile.tablet){ let selected_ctrl_items = []; const selection = new SelectionArea({ selectionContainerClass: '.selection-area-container', container: '.desktop', selectables: ['.desktop.item-container > .item'], startareas: ['.desktop'], boundaries: ['.desktop'], behaviour: { overlap: 'drop', intersect: 'touch', startThreshold: 10, scrolling: { speedDivider: 10, manualSpeed: 750, startScrollMargins: {x: 0, y: 0} } }, features: { touch: true, range: true, singleTap: { allow: true, intersect: 'native' } } }); selection.on('beforestart', ({event}) => { selected_ctrl_items = []; // Returning false prevents a selection return $(event.target).hasClass('item-container'); }) .on('beforedrag', evt => { }) .on('start', ({store, event}) => { if (!event.ctrlKey && !event.metaKey) { for (const el of store.stored) { el.classList.remove('item-selected'); } selection.clearSelection(); } }) .on('move', ({store: {changed: {added, removed}}, event}) => { for (const el of added) { // if ctrl or meta key is pressed and the item is already selected, then unselect it if((event.ctrlKey || event.metaKey) && $(el).hasClass('item-selected')){ el.classList.remove('item-selected'); selected_ctrl_items.push(el); } // otherwise select it else{ el.classList.add('item-selected'); } } for (const el of removed) { el.classList.remove('item-selected'); // in case this item was selected by ctrl+click before, then reselect it again if(selected_ctrl_items.includes(el)) $(el).not('.item-disabled').addClass('item-selected'); } }) .on('stop', evt => { }); } // ---------------------------------------------------- // User options // ---------------------------------------------------- let ht = ''; ht += `
`; // logo ht += ``; // create account button ht += ``; // 'show desktop' if(window.is_fullpage_mode){ ht += `Open Desktop`; } // refer if(user.referral_code){ ht += `
`; } // do not show the fullscreen button on mobile devices since it's broken if(!isMobile.phone){ // fullscreen button ht += `
`; } // qr code button -- only show if not embedded if(!is_embedded) ht += `
`; // user options menu ht += `
`; h += `${window.user.username}`; ht += `
`; ht += `
`; // prepend toolbar to desktop $(ht).insertBefore(el_desktop); // adjust window container to take into account the toolbar height $('.window-container').css('top', window.toolbar_height); // --------------------------------------------- // Run apps from insta-login URL // --------------------------------------------- if(url_query_params.has('app')){ let url_app_name = url_query_params.get('app'); if(url_app_name === 'explorer'){ let predefined_path = home_path; if(url_query_params.has('path')) predefined_path =url_query_params.get('path') // launch explorer UIWindow({ path: predefined_path, title: path.basename(predefined_path), icon: await item_icon({is_dir: true, path: predefined_path}), // todo // uid: $(el_item).attr('data-uid'), is_dir: true, // todo // sort_by: $(el_item).attr('data-sort_by'), app: 'explorer', }); } } // --------------------------------------------- // load from direct app URLs: /app/app-name // --------------------------------------------- else if(window.app_launched_from_url){ let qparams = new URLSearchParams(window.location.search); if(!qparams.has('c')){ launch_app({ name: app_launched_from_url, readURL: qparams.get('readURL'), maximized: qparams.get('maximized'), is_fullpage: window.is_fullpage_mode, window_options: { stay_on_top: false, } }); } } $(el_desktop).on('mousedown touchstart', function(e){ // dimiss touchstart on regular devices if(e.type==='taphold' && !isMobile.phone && !isMobile.tablet) return; // disable pointer-events for all app iframes, this is to make sure selectable works $('.window-app-iframe').css('pointer-events', 'none'); $('.window').find('.item-selected').addClass('item-blurred'); $('.desktop').find('.item-blurred').removeClass('item-blurred'); }) $(el_desktop).on('click', function(e){ // blur all windows $('.window-active').removeClass('window-active'); }) function display_ct() { var x = new Date() var ampm = x.getHours( ) >= 12 ? ' PM' : ' AM'; let hours = x.getHours( ) % 12; hours = hours ? hours : 12; hours=hours.toString().length==1? 0+hours.toString() : hours; var minutes=x.getMinutes().toString() minutes=minutes.length==1 ? 0+minutes : minutes; var seconds=x.getSeconds().toString() seconds=seconds.length==1 ? 0+seconds : seconds; var month=(x.getMonth() +1).toString(); month=month.length==1 ? 0+month : month; var dt=x.getDate().toString(); dt=dt.length==1 ? 0+dt : dt; var x1=month + "/" + dt + "/" + x.getFullYear(); x1 = x1 + " - " + hours + ":" + minutes + ":" + seconds + " " + ampm; $('#clock').html(x1); $('#clock').css('line-height', taskbar_height + 'px'); } setInterval(display_ct, 1000); // show referral notice window if(window.show_referral_notice && !user.email_confirmed){ getItem({ key: "shown_referral_notice", success: async function(res){ if(!res){ setTimeout(() => { UIWindowClaimReferral(); }, 1000); setItem({ key: "shown_referral_notice", value: true, }) } } }) } } $(document).on('contextmenu taphold', '.taskbar', function(event){ // dismiss taphold on regular devices if(event.type==='taphold' && !isMobile.phone && !isMobile.tablet) return; event.preventDefault(); event.stopPropagation(); UIContextMenu({ parent_element: $('.taskbar'), items: [ //-------------------------------------------------- // Show open windows //-------------------------------------------------- { html: "Show open windows", onClick: function(){ $(`.window`).showWindow(); } }, //-------------------------------------------------- // Show the desktop //-------------------------------------------------- { html: "Show the desktop", onClick: function(){ $(`.window`).hideWindow(); } } ] }); return false; }) $(document).on('click', '.qr-btn', async function (e) { UIWindowQR(); }) $(document).on('click', '.user-options-menu-btn', async function(e){ const pos = this.getBoundingClientRect(); if($('.context-menu[data-id="user-options-menu"]').length > 0) return; let items = []; let parent_element = this; //-------------------------------------------------- // Save Session //-------------------------------------------------- if(window.user.is_temp){ items.push( { html: `Save Session`, icon: ``, icon_active: ``, onClick: async function(){ UIWindowSaveAccount({ send_confirmation_code: false, default_username: window.user.username }); } }, ) // ------------------------------------------- // - // ------------------------------------------- items.push('-') } // ------------------------------------------- // Logged in users // ------------------------------------------- if(window.logged_in_users.length > 0){ let users_arr = window.logged_in_users; // bring logged in user's item to top users_arr.sort(function(x,y){ return x.uuid === window.user.uuid ? -1 : y.uuid == window.user.uuid ? 1 : 0; }); // create menu items users_arr.forEach(l_user => { items.push( { html: l_user.username, icon: l_user.username === user.username ? '✓' : '', onClick: async function(val){ // don't reload everything if clicked on already-logged-in user if(l_user.username === user.username) return; // update auth data update_auth_data(l_user.auth_token, l_user); // refresh location.reload(); } }, ) }); // ------------------------------------------- // - // ------------------------------------------- items.push('-') items.push( { html: 'Add existing account', // icon: l_user.username === user.username ? '✓' : '', onClick: async function(val){ await UIWindowLogin({ reload_on_success: true, send_confirmation_code: false, window_options:{ has_head: true } }); } }, ) // ------------------------------------------- // - // ------------------------------------------- items.push('-') } UIContextMenu({ id: 'user-options-menu', parent_element: parent_element, position: {top: pos.top + 28, left: pos.left + pos.width - 15}, items: [ ...items, //-------------------------------------------------- // My Websites //-------------------------------------------------- { html: "My Websites", onClick: async function(){ UIWindowMyWebsites(); } }, //-------------------------------------------------- // Change Username //-------------------------------------------------- { html: "Change Username", onClick: async function(){ UIWindowChangeUsername(); } }, //-------------------------------------------------- // Change Password //-------------------------------------------------- { html: "Change Password", onClick: async function(){ UIWindowChangePassword(); } }, //-------------------------------------------------- // Contact Us //-------------------------------------------------- { html: "Contact Us", onClick: async function(){ UIWindowFeedback(); } }, // ------------------------------------------- // - // ------------------------------------------- '-', //-------------------------------------------------- // Log Out //-------------------------------------------------- { html: "Log Out", onClick: async function(){ // see if there are any open windows, if yes notify user if($('.window-app').length > 0){ const alert_resp = await UIAlert({ message: `

You have open apps. Are you sure you want to log out?

`, buttons:[ { label: 'Close Windows and Log Out', type: 'primary', }, { label: 'Cancel' }, ] }) if(alert_resp === 'Close Windows and Log Out') logout(); } // no open windows else logout(); } }, ] }); }) $(document).on('click', '.fullscreen-btn', async function (e) { if(!is_fullscreen()) { var elem = document.documentElement; if (elem.requestFullscreen) { elem.requestFullscreen(); } else if (elem.webkitRequestFullscreen) { /* Safari */ elem.webkitRequestFullscreen(); } else if (elem.mozRequestFullScreen) { /* moz */ elem.mozRequestFullScreen(); } else if (elem.msRequestFullscreen) { /* IE11 */ elem.msRequestFullscreen(); } } else{ if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } } }) $(document).on('click', '.close-launch-popover', function(){ $(".launch-popover").closest('.popover').fadeOut(200, function(){ $(".launch-popover").closest('.popover').remove(); }); }); $(document).on('click', '.toolbar-puter-logo', function(){ // launch the about app launch_app({name: 'about', window_options:{ single_instance: true, }}); }) $(document).on('click', '.user-options-create-account-btn', async function(e){ UIWindowSaveAccount({ send_confirmation_code: false, default_username: window.user.username, }); }) $(document).on('click', '.refer-btn', async function(e){ UIWindowRefer(); }) $(document).on('click', '.start-app', async function(e){ launch_app({ name: $(this).attr('data-app-name') }) // close popovers $(".popover").fadeOut(200, function(){ $(".popover").remove(); }); }) $(document).on('click', '.user-options-login-btn', async function(e){ const alert_resp = await UIAlert({ message: `Save session before exiting!

You are in a temporary session and logging into another account will erase all data in your current session.

`, buttons:[ { label: 'Save session', value: 'save-session', type: 'primary', }, { label: 'Log into another account anyway', value: 'login', }, { label: 'Cancel' }, ] }) if(alert_resp === 'save-session'){ let saved = await UIWindowSaveAccount({ send_confirmation_code: false, }); if(saved) UIWindowLogin({show_signup_button: false, reload_on_success: true}); }else if (alert_resp === 'login'){ UIWindowLogin({ show_signup_button: false, reload_on_success: true, window_options: { backdrop: true, close_on_backdrop_click: false, } }); } }) $(document).on('click mousedown', '.launch-search, .launch-popover', function(e){ $(this).focus(); e.stopPropagation(); e.preventDefault(); // don't let click bubble up to window e.stopImmediatePropagation(); }) $(document).on('focus', '.launch-search', function(e){ // remove all selected items in start menu $('.launch-app-selected').removeClass('launch-app-selected'); // scroll popover to top $('.launch-popover').scrollTop(0); }) $(document).on('change keyup keypress keydown paste', '.launch-search', function(e){ // search launch_apps.recommended for query const query = $(this).val().toLowerCase(); if(query === ''){ $('.launch-search-clear').hide(); $(`.start-app-card`).show(); $('.launch-apps-recent').show(); $('.start-section-heading').show(); }else{ $('.launch-apps-recent').hide(); $('.start-section-heading').hide(); $('.launch-search-clear').show(); launch_apps.recommended.forEach((app)=>{ if(app.title.toLowerCase().includes(query.toLowerCase())){ $(`.start-app-card[data-name="${app.name}"]`).show(); }else{ $(`.start-app-card[data-name="${app.name}"]`).hide(); } }) } }) $(document).on('click', '.launch-search-clear', function(e){ $('.launch-search').val(''); $('.launch-search').trigger('change'); $('.launch-search').focus(); }) document.addEventListener('fullscreenchange', (event) => { // document.fullscreenElement will point to the element that // is in fullscreen mode if there is one. If there isn't one, // the value of the property is null. if (document.fullscreenElement) { $('.fullscreen-btn').css('background-image', `url(${window.icons['shrink.svg']})`); $('.fullscreen-btn').attr('title', 'Exit Full Screen'); $('#clock').show(); } else { $('.fullscreen-btn').css('background-image', `url(${window.icons['fullscreen.svg']})`); $('.fullscreen-btn').attr('title', 'Enter Full Screen'); $('#clock').hide(); } }) window.set_desktop_background = function(options){ if(options.fit){ let fit = options.fit; if(fit === 'cover' || fit === 'contain'){ $('body').css('background-size', fit); $('body').css('background-repeat', `no-repeat`); $('body').css('background-position', `center center`); } else if(fit === 'center'){ $('body').css('background-size', 'auto'); $('body').css('background-repeat', `no-repeat`); $('body').css('background-position', `center center`); } else if( fit === 'repeat'){ $('body').css('background-size', `auto`); $('body').css('background-repeat', `repeat`); } window.desktop_bg_fit = fit; } if(options.url){ $('body').css('background-image', `url(${options.url})`); window.desktop_bg_url = options.url; window.desktop_bg_color = undefined; } else if(options.color){ $('body').css({ 'background-image': `none`, 'background-color': options.color, }); window.desktop_bg_color = options.color; window.desktop_bg_url = undefined; } } window.update_taskbar = function(){ let items = [] $('.taskbar-item-sortable[data-keep-in-taskbar="true"]').each(function(index){ items.push({ name: $( this ).attr('data-app'), type: 'app', }) }) // update taskbar in the server-side $.ajax({ url: api_origin + "/update-taskbar-items", type: 'POST', data: JSON.stringify({ items: items, }), async: true, contentType: "application/json", headers: { "Authorization": "Bearer "+auth_token }, }) } window.remove_taskbar_item = function(item){ $(item).find('*').fadeOut(100, function(){}); $(item).animate({width: 0}, 200, function(){ $(item).remove(); }) } window.enter_fullpage_mode = (el_window)=>{ $('.taskbar').hide(); $(el_window).find('.window-head').hide(); $('body').addClass('fullpage-mode'); $(el_window).css({ width: '100%', height: '100%', top: toolbar_height + 'px', left: 0, 'border-radius': 0, }); } window.exit_fullpage_mode = (el_window)=>{ $('body').removeClass('fullpage-mode'); window.taskbar_height = window.default_taskbar_height; $('.taskbar').css('height', window.taskbar_height); $('.taskbar').show(); refresh_item_container($('.desktop.item-container'), {fadeInItems: true}); $(el_window).removeAttr('data-is_fullpage'); if(el_window){ reset_window_size_and_position(el_window) $(el_window).find('.window-head').show(); } // reset dektop height to take into account the taskbar height $('.desktop').css('height', `calc(100vh - ${window.taskbar_height + window.toolbar_height}px)`); // hide the 'Show Desktop' button in toolbar $('.show-desktop-btn').hide(); // refresh desktop background refresh_desktop_background(); } window.reset_window_size_and_position = (el_window)=>{ $(el_window).css({ width: 680, height: 380, 'border-radius': window_border_radius, top: 'calc(50% - 190px)', left: 'calc(50% - 340px)', }); } export default UIDesktop;