mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-05 00:50:41 +00:00
2530 lines
100 KiB
JavaScript
2530 lines
100 KiB
JavaScript
/**
|
|
* Copyright (C) 2024-present 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
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 UIWindowFeedback from "./UIWindowFeedback.js"
|
|
import UIWindowLogin from "./UIWindowLogin.js"
|
|
import UIWindowQR from "./UIWindowQR.js"
|
|
import UIWindowRefer from "./UIWindowRefer.js"
|
|
import UIWindowProgress from "./UIWindowProgress.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"
|
|
import UIWindowTaskManager from "./UIWindowTaskManager.js"
|
|
import truncate_filename from '../helpers/truncate_filename.js';
|
|
import UINotification from "./UINotification.js"
|
|
import UIWindowWelcome from "./UIWindowWelcome.js"
|
|
import launch_app from "../helpers/launch_app.js"
|
|
import item_icon from "../helpers/item_icon.js"
|
|
import UIWindowSearch from "./UIWindowSearch.js"
|
|
|
|
async function UIDesktop(options) {
|
|
// start a transaction if we're not in embedded or fullpage mode
|
|
let transaction;
|
|
if (!window.is_embedded && !window.is_fullpage_mode) {
|
|
transaction = new window.Transaction('desktop-is-ready');
|
|
transaction.start();
|
|
}
|
|
|
|
let h = '';
|
|
|
|
// Set up the desktop channel for communication between different tabs in the same browser
|
|
window.channel = new BroadcastChannel('puter-desktop-channel');
|
|
channel.onmessage = function (e) {
|
|
}
|
|
|
|
// Initialize desktop icons visibility preference - move this earlier in the initialization
|
|
// Add this near the very beginning of the UIDesktop function
|
|
window.desktop_icons_hidden = false; // Set default value immediately
|
|
|
|
// Initialize toolbar auto-hide preference
|
|
window.toolbar_auto_hide_enabled = true; // Set default value
|
|
|
|
// Load the toolbar auto-hide preference
|
|
let toolbar_auto_hide_enabled_val = await puter.kv.get('toolbar_auto_hide_enabled');
|
|
if(toolbar_auto_hide_enabled_val === 'false' || toolbar_auto_hide_enabled_val === false){
|
|
window.toolbar_auto_hide_enabled = false;
|
|
}
|
|
|
|
// Give Camera and Recorder write permissions to Desktop
|
|
puter.kv.get('has_set_default_app_user_permissions').then(async (user_permissions) => {
|
|
if (!user_permissions) {
|
|
// Camera
|
|
try {
|
|
await fetch(window.api_origin + "/auth/grant-user-app", {
|
|
"headers": {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "Bearer " + window.auth_token,
|
|
},
|
|
"body": JSON.stringify({
|
|
app_uid: 'app-5584fbf7-ed69-41fc-99cd-85da21b1ef51',
|
|
permission: `fs:${html_encode(window.desktop_path)}:write`
|
|
}),
|
|
"method": "POST",
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
|
|
// Recorder
|
|
try {
|
|
await fetch(window.api_origin + "/auth/grant-user-app", {
|
|
"headers": {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "Bearer " + window.auth_token,
|
|
},
|
|
"body": JSON.stringify({
|
|
app_uid: 'app-7bdca1a4-6373-4c98-ad97-03ff2d608ca1',
|
|
permission: `fs:${html_encode(window.desktop_path)}:write`
|
|
}),
|
|
"method": "POST",
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
|
|
// Set flag to true
|
|
puter.kv.set('has_set_default_app_user_permissions', true);
|
|
}
|
|
})
|
|
// connect socket.
|
|
window.socket = io(window.gui_origin + '/', {
|
|
auth: {
|
|
auth_token: window.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.emit('puter_is_actually_open');
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
window.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;
|
|
}
|
|
}
|
|
});
|
|
|
|
window.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;
|
|
}
|
|
}
|
|
});
|
|
|
|
window.socket.on('trash.is_empty', async (msg) => {
|
|
$(`.item[data-path="${html_encode(window.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(window.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(window.trash_path)}" i]`).find('.item-container').empty();
|
|
})
|
|
|
|
/**
|
|
* This event is triggered if a user receives a notification during
|
|
* an active session.
|
|
*/
|
|
window.socket.on('notif.message', async ({ uid, notification }) => {
|
|
let icon = window.icons[notification.icon];
|
|
let round_icon = false;
|
|
|
|
if (notification.template === "file-shared-with-you" && notification.fields?.username) {
|
|
let profile_pic = await get_profile_picture(notification.fields?.username);
|
|
if (profile_pic) {
|
|
icon = profile_pic;
|
|
round_icon = true;
|
|
}
|
|
}
|
|
|
|
UINotification({
|
|
title: notification.title,
|
|
text: notification.text,
|
|
icon: icon,
|
|
round_icon: round_icon,
|
|
value: notification,
|
|
uid,
|
|
close: async () => {
|
|
await fetch(`${window.api_origin}/notif/mark-ack`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${puter.authToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ uid }),
|
|
});
|
|
},
|
|
click: async (notif) => {
|
|
if (notification.template === "file-shared-with-you") {
|
|
let item_path = '/' + notification.fields.username;
|
|
UIWindow({
|
|
path: '/' + notification.fields.username,
|
|
title: path.basename(item_path),
|
|
icon: await item_icon({ is_dir: true, path: item_path }),
|
|
is_dir: true,
|
|
app: 'explorer',
|
|
});
|
|
}
|
|
},
|
|
});
|
|
});
|
|
|
|
/**
|
|
* This event is triggered at the beginning of the session, after a websocket
|
|
* connection is established, because the backend informs the frontend of all
|
|
* unread notifications.
|
|
*
|
|
* It is not necessary to query unreads separately. If this stops working,
|
|
* then this event should be fixed rather than querying unreads separately.
|
|
*/
|
|
window.__already_got_unreads = false;
|
|
window.socket.on('notif.unreads', async ({ unreads }) => {
|
|
if (window.__already_got_unreads) return;
|
|
window.__already_got_unreads = true;
|
|
|
|
for (const notif_info of unreads) {
|
|
const notification = notif_info.notification;
|
|
let icon = window.icons[notification.icon];
|
|
let round_icon = false;
|
|
|
|
if (notification.template === "file-shared-with-you" && notification.fields?.username) {
|
|
let profile_pic = await get_profile_picture(notification.fields?.username);
|
|
if (profile_pic) {
|
|
icon = profile_pic;
|
|
round_icon = true;
|
|
}
|
|
}
|
|
|
|
UINotification({
|
|
icon,
|
|
round_icon,
|
|
title: notification.title,
|
|
text: notification.text ?? notification.title,
|
|
uid: notif_info.uid,
|
|
close: async () => {
|
|
await fetch(`${window.api_origin}/notif/mark-ack`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${puter.authToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
uid: notif_info.uid,
|
|
}),
|
|
});
|
|
},
|
|
click: async (notif) => {
|
|
if (notification.template === "file-shared-with-you") {
|
|
let item_path = '/' + notification.fields?.username;
|
|
UIWindow({
|
|
path: '/' + notification.fields?.username,
|
|
title: path.basename(item_path),
|
|
icon: await item_icon({ is_dir: true, path: item_path }),
|
|
is_dir: true,
|
|
app: 'explorer',
|
|
});
|
|
}
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
window.socket.on('notif.ack', ({ uid }) => {
|
|
$(`.notification[data-uid="${uid}"]`).remove();
|
|
update_tab_notif_count_badge();
|
|
});
|
|
|
|
window.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
|
|
window.launch_apps.recent.unshift(app);
|
|
|
|
// dedupe the array by uuid, uid, and id
|
|
window.launch_apps.recent = _.uniqBy(window.launch_apps.recent, 'name');
|
|
|
|
// limit to 5
|
|
window.launch_apps.recent = window.launch_apps.recent.slice(0, window.launch_recent_apps_count);
|
|
})
|
|
|
|
window.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();
|
|
});
|
|
})
|
|
|
|
window.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)));
|
|
|
|
// 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 () {
|
|
window.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 () {
|
|
window.sort_items(this, $(this).closest('.item-container').attr('data-sort_by'), $(this).closest('.item-container').attr('data-sort_order'));
|
|
})
|
|
})
|
|
|
|
window.socket.on('item.moved', async (resp) => {
|
|
let fsentry = resp;
|
|
// Notify all apps that are watching this item
|
|
window.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;
|
|
|
|
// 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) {
|
|
window.update_explorer_footer_item_count(this);
|
|
window.update_explorer_footer_selected_items_count(this)
|
|
});
|
|
})
|
|
|
|
// if trashing, close windows of trashed items and its descendants
|
|
if (dest_path === window.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 () {
|
|
window.update_window_path(this, $(this).attr('data-path').replace(resp.old_path, fsentry.path));
|
|
})
|
|
}
|
|
|
|
if (dest_path === window.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 || fsentry.writable === false,
|
|
uid: fsentry.uid,
|
|
path: fsentry.path,
|
|
icon: await item_icon(fsentry),
|
|
name: (dest_path === window.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 === window.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,
|
|
});
|
|
}
|
|
window.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 () {
|
|
window.sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order'))
|
|
})
|
|
});
|
|
|
|
window.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;
|
|
|
|
window.refresh_user_data(window.auth_token);
|
|
});
|
|
|
|
window.socket.on('user.email_changed', (msg) => {
|
|
// don't update if this is the original client that initiated the action
|
|
if (msg.original_client_socket_id === window.socket.id)
|
|
return;
|
|
|
|
window.refresh_user_data(window.auth_token);
|
|
});
|
|
|
|
window.socket.on('item.renamed', async (item) => {
|
|
// Notify all apps that are watching this item
|
|
window.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)));
|
|
|
|
// 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 () {
|
|
window.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 () {
|
|
window.sort_items(this, $(this).closest('.item-container').attr('data-sort_by'), $(this).closest('.item-container').attr('data-sort_order'));
|
|
})
|
|
});
|
|
|
|
window.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
|
|
window.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 () {
|
|
window.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 || item.writable === false,
|
|
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,
|
|
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 () {
|
|
window.sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order'))
|
|
})
|
|
}
|
|
});
|
|
|
|
// Hidden file dialog
|
|
h += `<form name="upload-form" id="upload-form" style="display:hidden;">
|
|
<input type="hidden" name="name" id="upload-filename" value="">
|
|
<input type="hidden" name="path" id="upload-target-path" value="">
|
|
<input type="file" name="file" id="upload-file-dialog" style="display: none;" multiple="multiple">
|
|
</form>`;
|
|
|
|
h += `<div class="window-container"></div>`;
|
|
|
|
// 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 += `<div class="desktop item-container disable-user-select"
|
|
data-uid="${options.desktop_fsentry.uid}"
|
|
data-sort_by="${!options.desktop_fsentry.sort_by ? 'name' : options.desktop_fsentry.sort_by}"
|
|
data-sort_order="${!options.desktop_fsentry.sort_order ? 'asc' : options.desktop_fsentry.sort_order}"
|
|
data-path="${html_encode(window.desktop_path)}"
|
|
>`;
|
|
|
|
// show AI button
|
|
if(window.user.email_confirmed){
|
|
h += `<div class="btn-show-ai"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles-icon lucide-sparkles"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg></div>`;
|
|
}
|
|
h += `</div>`;
|
|
|
|
// Get window sidebar width
|
|
puter.kv.get('window_sidebar_width').then(async (val) => {
|
|
let value = parseInt(val);
|
|
// if value is a valid number
|
|
if (!isNaN(value) && value > 0) {
|
|
window.window_sidebar_width = value;
|
|
}
|
|
})
|
|
|
|
// load window sidebar items from KV
|
|
puter.kv.get("sidebar_items").then(async (val) => {
|
|
window.sidebar_items = val;
|
|
})
|
|
|
|
// Remove `?ref=...` from navbar URL
|
|
if (window.url_query_params.has('ref')) {
|
|
window.history.pushState(null, document.title, '/');
|
|
}
|
|
|
|
// update local user preferences
|
|
const user_preferences = {
|
|
show_hidden_files: JSON.parse(await puter.kv.get('user_preferences.show_hidden_files')),
|
|
language: await puter.kv.get('user_preferences.language'),
|
|
clock_visible: await puter.kv.get('user_preferences.clock_visible'),
|
|
};
|
|
|
|
// update default apps
|
|
{
|
|
const entries = await puter.kv.list('user_preferences.default_apps.*', true);
|
|
for ( const entry of entries ) {
|
|
user_preferences[entry.key.substring(17)] = entry.value;
|
|
}
|
|
|
|
window.update_user_preferences(user_preferences);
|
|
}
|
|
|
|
// Append to <body>
|
|
$('body').append(h);
|
|
|
|
// Set desktop height based on taskbar height
|
|
$('.desktop').css('height', `calc(100vh - ${window.taskbar_height + window.toolbar_height}px)`)
|
|
|
|
// Initialize the preference early
|
|
puter.kv.get('desktop_icons_hidden').then(async (val) => {
|
|
window.desktop_icons_hidden = (val === 'true' || val === true);
|
|
|
|
// Apply the setting immediately if needed
|
|
if (window.desktop_icons_hidden) {
|
|
hideDesktopIcons();
|
|
}
|
|
});
|
|
|
|
// ---------------------------------------------------------------
|
|
// Taskbar
|
|
// ---------------------------------------------------------------
|
|
UITaskbar();
|
|
|
|
// Update desktop dimensions after taskbar is initialized with position
|
|
window.update_desktop_dimensions_for_taskbar();
|
|
|
|
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) {
|
|
window.upload_items(e.dataTransfer.items, window.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 (window.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')) === window.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 < cloned_items.length; i++) {
|
|
const source_item = document.getElementById('item-' + $(cloned_items[i]).attr('data-id'));
|
|
if (source_item !== null)
|
|
items_to_move.push(source_item);
|
|
}
|
|
|
|
// if ctrl key is down, copy items
|
|
if (event.ctrlKey) {
|
|
// unless source is Trash
|
|
if (path.dirname($(ui.draggable).attr('data-path')) === window.trash_path)
|
|
return;
|
|
|
|
window.copy_items(items_to_move, window.desktop_path)
|
|
}
|
|
// otherwise, move items
|
|
else {
|
|
window.move_items(items_to_move, window.desktop_path);
|
|
}
|
|
}
|
|
});
|
|
|
|
//--------------------------------------------------
|
|
// ContextMenu
|
|
//--------------------------------------------------
|
|
$(el_desktop).bind("contextmenu taphold", function (event) {
|
|
// dismiss taphold on regular devices
|
|
if (event.type === 'taphold' && !isMobile.phone && !isMobile.tablet)
|
|
return;
|
|
|
|
const $target = $(event.target);
|
|
|
|
// elements that should retain native ctxmenu
|
|
if ($target.is('input') || $target.is('textarea'))
|
|
return true
|
|
|
|
// custom ctxmenu for all other elements
|
|
if (event.target === el_desktop) {
|
|
event.preventDefault();
|
|
UIContextMenu({
|
|
position: event.type === 'taphold' ? undefined : { left: event.pageX, top: event.pageY },
|
|
items: [
|
|
// -------------------------------------------
|
|
// Sort by
|
|
// -------------------------------------------
|
|
{
|
|
html: i18n('sort_by'),
|
|
items: [
|
|
{
|
|
html: i18n('auto_arrange'),
|
|
icon: window.is_auto_arrange_enabled ? '✓' : '',
|
|
onClick: async function () {
|
|
window.is_auto_arrange_enabled = !window.is_auto_arrange_enabled;
|
|
window.store_auto_arrange_preference(window.is_auto_arrange_enabled);
|
|
if (window.is_auto_arrange_enabled) {
|
|
window.sort_items(el_desktop, $(el_desktop).attr('data-sort_by'), $(el_desktop).attr('data-sort_order'));
|
|
window.set_sort_by(options.desktop_fsentry.uid, $(el_desktop).attr('data-sort_by'), $(el_desktop).attr('data-sort_order'))
|
|
window.clear_desktop_item_positions(el_desktop);
|
|
} else {
|
|
window.set_desktop_item_positions(el_desktop)
|
|
}
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// -
|
|
// -------------------------------------------
|
|
'-',
|
|
{
|
|
html: i18n('name'),
|
|
disabled: !window.is_auto_arrange_enabled,
|
|
icon: $(el_desktop).attr('data-sort_by') === 'name' ? '✓' : '',
|
|
onClick: async function () {
|
|
window.sort_items(el_desktop, 'name', $(el_desktop).attr('data-sort_order'));
|
|
window.set_sort_by(options.desktop_fsentry.uid, 'name', $(el_desktop).attr('data-sort_order'))
|
|
}
|
|
},
|
|
{
|
|
html: i18n('date_modified'),
|
|
disabled: !window.is_auto_arrange_enabled,
|
|
icon: $(el_desktop).attr('data-sort_by') === 'modified' ? '✓' : '',
|
|
onClick: async function () {
|
|
window.sort_items(el_desktop, 'modified', $(el_desktop).attr('data-sort_order'));
|
|
window.set_sort_by(options.desktop_fsentry.uid, 'modified', $(el_desktop).attr('data-sort_order'))
|
|
}
|
|
},
|
|
{
|
|
html: i18n('type'),
|
|
disabled: !window.is_auto_arrange_enabled,
|
|
icon: $(el_desktop).attr('data-sort_by') === 'type' ? '✓' : '',
|
|
onClick: async function () {
|
|
window.sort_items(el_desktop, 'type', $(el_desktop).attr('data-sort_order'));
|
|
window.set_sort_by(options.desktop_fsentry.uid, 'type', $(el_desktop).attr('data-sort_order'))
|
|
}
|
|
},
|
|
{
|
|
html: i18n('size'),
|
|
disabled: !window.is_auto_arrange_enabled,
|
|
icon: $(el_desktop).attr('data-sort_by') === 'size' ? '✓' : '',
|
|
onClick: async function () {
|
|
window.sort_items(el_desktop, 'size', $(el_desktop).attr('data-sort_order'));
|
|
window.set_sort_by(options.desktop_fsentry.uid, 'size', $(el_desktop).attr('data-sort_order'))
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// -
|
|
// -------------------------------------------
|
|
'-',
|
|
{
|
|
html: i18n('ascending'),
|
|
disabled: !window.is_auto_arrange_enabled,
|
|
icon: $(el_desktop).attr('data-sort_order') === 'asc' ? '✓' : '',
|
|
onClick: async function () {
|
|
const sort_by = $(el_desktop).attr('data-sort_by')
|
|
window.sort_items(el_desktop, sort_by, 'asc');
|
|
window.set_sort_by(options.desktop_fsentry.uid, sort_by, 'asc')
|
|
}
|
|
},
|
|
{
|
|
html: i18n('descending'),
|
|
disabled: !window.is_auto_arrange_enabled,
|
|
icon: $(el_desktop).attr('data-sort_order') === 'desc' ? '✓' : '',
|
|
onClick: async function () {
|
|
const sort_by = $(el_desktop).attr('data-sort_by')
|
|
window.sort_items(el_desktop, sort_by, 'desc');
|
|
window.set_sort_by(options.desktop_fsentry.uid, sort_by, 'desc')
|
|
}
|
|
},
|
|
]
|
|
},
|
|
// -------------------------------------------
|
|
// Refresh
|
|
// -------------------------------------------
|
|
{
|
|
html: i18n('refresh'),
|
|
onClick: function () {
|
|
refresh_item_container(el_desktop, { consistency: 'strong' });
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// Show/Hide hidden files
|
|
// -------------------------------------------
|
|
{
|
|
html: i18n('show_hidden'),
|
|
icon: window.user_preferences.show_hidden_files ? '✓' : '',
|
|
onClick: function () {
|
|
window.mutate_user_preferences({
|
|
show_hidden_files: !window.user_preferences.show_hidden_files,
|
|
});
|
|
window.show_or_hide_files(document.querySelectorAll('.item-container'));
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// Hide Desktop Icons
|
|
// -------------------------------------------
|
|
{
|
|
html: window.desktop_icons_hidden ? i18n('Show desktop icons') : i18n('Hide desktop icons'),
|
|
onClick: function () {
|
|
toggleDesktopIcons();
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// -
|
|
// -------------------------------------------
|
|
'-',
|
|
// -------------------------------------------
|
|
// New File
|
|
// -------------------------------------------
|
|
new_context_menu_item(window.desktop_path, el_desktop),
|
|
// -------------------------------------------
|
|
// -
|
|
// -------------------------------------------
|
|
'-',
|
|
// -------------------------------------------
|
|
// Paste
|
|
// -------------------------------------------
|
|
{
|
|
html: i18n('paste'),
|
|
disabled: window.clipboard.length > 0 ? false : true,
|
|
onClick: function () {
|
|
if (window.clipboard_op === 'copy')
|
|
window.copy_clipboard_items(window.desktop_path, el_desktop);
|
|
else if (window.clipboard_op === 'move')
|
|
window.move_clipboard_items(el_desktop)
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// Undo
|
|
// -------------------------------------------
|
|
{
|
|
html: i18n('undo'),
|
|
disabled: window.actions_history.length > 0 ? false : true,
|
|
onClick: function () {
|
|
window.undo_last_action();
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// Upload Here
|
|
// -------------------------------------------
|
|
{
|
|
html: i18n('upload_here'),
|
|
onClick: function () {
|
|
window.init_upload_using_dialog(el_desktop);
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// -
|
|
// -------------------------------------------
|
|
'-',
|
|
// -------------------------------------------
|
|
// Change Desktop Background…
|
|
// -------------------------------------------
|
|
{
|
|
html: i18n('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 (!window.is_embedded && !window.is_fullpage_mode) {
|
|
refresh_item_container(el_desktop, {
|
|
fadeInItems: true,
|
|
onComplete: () => {
|
|
// End transaction when desktop is fully ready for user interaction
|
|
transaction.end();
|
|
}
|
|
})
|
|
|
|
// perform readdirs for caching purposes
|
|
|
|
// home directory
|
|
puter.fs.readdir({path: window.home_path, consistency: 'strong'});
|
|
|
|
// Show welcome window if user hasn't already seen it and hasn't directly navigated to an app
|
|
if (!window.url_paths[0]?.toLocaleLowerCase() === 'app' || !window.url_paths[1]) {
|
|
if (!isMobile.phone && !isMobile.tablet) {
|
|
setTimeout(() => {
|
|
puter.kv.get('has_seen_welcome_window').then(async (val) => {
|
|
if (val === null) {
|
|
await UIWindowWelcome();
|
|
}
|
|
})
|
|
}, 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------
|
|
// 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();
|
|
}
|
|
|
|
// mark desktop as selectable active
|
|
$('.desktop').addClass('desktop-selectable-active');
|
|
})
|
|
.on('move', ({ store: { changed: { added, removed } }, event }) => {
|
|
window.desktop_selectable_is_active = true;
|
|
|
|
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 => {
|
|
window.desktop_selectable_is_active = false;
|
|
$('.desktop').removeClass('desktop-selectable-active');
|
|
});
|
|
}
|
|
// ----------------------------------------------------
|
|
// Toolbar
|
|
// ----------------------------------------------------
|
|
// Has user seen the toolbar animation?
|
|
window.has_seen_toolbar_animation = await puter.kv.get('has_seen_toolbar_animation') ?? false;
|
|
|
|
let ht = '';
|
|
let style = '';
|
|
let class_name = '';
|
|
if(window.has_seen_toolbar_animation && !isMobile.phone && !isMobile.tablet){
|
|
style = 'top: -20px; width: 40px;';
|
|
class_name = 'toolbar-hidden';
|
|
}else{
|
|
style= 'height:30px; min-height:30px; max-height:30px;';
|
|
}
|
|
|
|
ht += `<div class="toolbar hide-scrollbar ${class_name}" style="${style}">`;
|
|
// logo
|
|
ht += `<div class="toolbar-btn toolbar-puter-logo" title="Puter" style="margin-left: 10px;"><img src="${window.icons['logo-white.svg']}" draggable="false" style="display:block; width:17px; height:17px"></div>`;
|
|
|
|
// clock spacer
|
|
ht += `<div class="toolbar-spacer"></div>`;
|
|
|
|
// create account button
|
|
ht += `<div class="toolbar-btn user-options-create-account-btn ${window.user.is_temp ? '' : 'hidden'}" style="padding:0; opacity:1;" title="${i18n('toolbar.save_account')}">`;
|
|
ht += `<svg style="width: 17px; height: 17px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px" height="48px" viewBox="0 0 48 48"><g transform="translate(0, 0)"><path d="M45.521,39.04L27.527,5.134c-1.021-1.948-3.427-2.699-5.375-1.679-.717,.376-1.303,.961-1.679,1.679L2.479,39.04c-.676,1.264-.635,2.791,.108,4.017,.716,1.207,2.017,1.946,3.42,1.943H41.993c1.403,.003,2.704-.736,3.42-1.943,.743-1.226,.784-2.753,.108-4.017ZM23.032,15h1.937c.565,0,1.017,.467,1,1.031l-.438,14c-.017,.54-.459,.969-1,.969h-1.062c-.54,0-.983-.429-1-.969l-.438-14c-.018-.564,.435-1.031,1-1.031Zm.968,25c-1.657,0-3-1.343-3-3s1.343-3,3-3,3,1.343,3,3-1.343,3-3,3Z" fill="#ffbb00"></path></g></svg>`;
|
|
ht += `</div>`;
|
|
|
|
// 'Show Desktop'
|
|
ht += `<a href="/" class="show-desktop-btn toolbar-btn antialiased hidden" target="_blank" title="${i18n('desktop_show_desktop')}">${i18n('desktop_show_desktop')} <img src="${window.icons['launch-white.svg']}" style="width: 10px; height: 10px; margin-left: 5px;"></a>`;
|
|
|
|
// refer
|
|
if (window.user.referral_code) {
|
|
ht += `<div class="toolbar-btn refer-btn" title="${i18n('toolbar.refer')}" style="background-image:url(${window.icons['gift.svg']});"></div>`;
|
|
}
|
|
|
|
// github
|
|
ht += `<a href="https://github.com/HeyPuter/puter" target="_blank" class="toolbar-btn" title="${i18n('toolbar.github')}" style="background-image:url(${window.icons['logo-github-white.svg']});"></a>`;
|
|
|
|
// do not show the fullscreen button on mobile devices since it's broken
|
|
if (!isMobile.phone) {
|
|
// fullscreen button
|
|
ht += `<div class="toolbar-btn fullscreen-btn" title="${i18n('toolbar.enter_fullscreen')}" style="background-image:url(${window.icons['fullscreen.svg']})"></div>`;
|
|
}
|
|
|
|
// qr code button -- only show if not embedded
|
|
if (!window.is_embedded)
|
|
ht += `<div class="toolbar-btn qr-btn" title="${i18n('toolbar.qrcode')}" style="background-image:url(${window.icons['qr.svg']})"></div>`;
|
|
|
|
// search button
|
|
ht += `<div class="toolbar-btn search-btn" title="${i18n('toolbar.search')}" style="background-image:url('${window.icons['search.svg']}')"></div>`;
|
|
|
|
//clock
|
|
ht += `<div id="clock" class="toolbar-clock" style="">12:00 AM Sun, Jan 01</div>`;
|
|
|
|
// user options menu
|
|
ht += `<div class="toolbar-btn user-options-menu-btn profile-pic" style="display:block;">`;
|
|
ht += `<div class="profile-image ${window.user?.profile?.picture && 'profile-image-has-picture'}" style="border-radius: 50%; background-image:url(${window.user?.profile?.picture || window.icons['profile.svg']}); box-sizing: border-box; width: 17px !important; height: 17px !important; background-size: contain; background-repeat: no-repeat; background-position: center; background-position: center; background-size: cover;"></div>`;
|
|
ht += `</div>`;
|
|
ht += `</div>`;
|
|
|
|
// prepend toolbar to desktop
|
|
$(ht).insertBefore(el_desktop);
|
|
|
|
// If auto-hide is disabled, ensure toolbar is visible on load
|
|
if (!window.toolbar_auto_hide_enabled) {
|
|
// Make sure toolbar is visible when auto-hide is disabled
|
|
setTimeout(() => {
|
|
if ($('.toolbar').hasClass('toolbar-hidden')) {
|
|
window.show_toolbar();
|
|
}
|
|
}, 100); // Small delay to ensure DOM is ready
|
|
}
|
|
|
|
// send event
|
|
window.dispatchEvent(new CustomEvent('toolbar:ready'));
|
|
// init clock visibility
|
|
window.change_clock_visible();
|
|
|
|
// notification container
|
|
$('body').append(`<div class="notification-container"><div class="notifications-close-all">${i18n('close_all')}</div></div>`);
|
|
|
|
// adjust window container to take into account the toolbar height
|
|
$('.window-container').css('top', window.toolbar_height);
|
|
|
|
// track: checkpoint
|
|
//-----------------------------
|
|
// GUI is ready to launch apps!
|
|
//-----------------------------
|
|
window.dispatchEvent(new CustomEvent('desktop:ready'));
|
|
globalThis.services.emit('gui:ready');
|
|
|
|
//--------------------------------------------------------
|
|
// Open the AI app
|
|
//--------------------------------------------------------
|
|
if(window.user.email_confirmed){
|
|
launch_app({
|
|
name: 'ai',
|
|
window_options: {
|
|
is_panel: true,
|
|
}
|
|
})
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
// Determine if an app was launched from URL
|
|
// i.e. https://puter.com/app/<app_name>
|
|
//--------------------------------------------------------------------------------------
|
|
if (window.url_paths[0]?.toLocaleLowerCase() === 'app' && window.url_paths[1]) {
|
|
window.app_launched_from_url = window.url_paths[1];
|
|
// get app metadata
|
|
try {
|
|
window.app_launched_from_url = await puter.apps.get(window.url_paths[1], { icon_size: 64 })
|
|
window.is_fullpage_mode = window.app_launched_from_url.metadata?.fullpage_on_landing ?? window.is_fullpage_mode ?? false;
|
|
|
|
// show 'Show Desktop' button
|
|
if (window.is_fullpage_mode) {
|
|
$('.show-desktop-btn').removeClass('hidden');
|
|
}
|
|
} catch (e) {
|
|
console.error('UIDesktop app path launch error', e);
|
|
}
|
|
|
|
// get query params, any param that doesn't start with 'puter.' will be passed to the app
|
|
window.app_query_params = {};
|
|
for (let [key, value] of window.url_query_params) {
|
|
if (!key.startsWith('puter.'))
|
|
window.app_query_params[key] = value;
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------
|
|
// /settings will open settings in fullpage mode
|
|
//--------------------------------------------------------------------------------------
|
|
else if (window.url_paths[0]?.toLocaleLowerCase() === 'settings') {
|
|
// open settings
|
|
UIWindowSettings({
|
|
tab: window.url_paths[1] || 'about',
|
|
window_options: {
|
|
is_fullpage: true,
|
|
}
|
|
});
|
|
}
|
|
// ---------------------------------------------
|
|
// Run apps from insta-login URL
|
|
// ---------------------------------------------
|
|
if (window.url_query_params.has('app')) {
|
|
let url_app_name = window.url_query_params.get('app');
|
|
if (url_app_name === 'explorer') {
|
|
let predefined_path = window.home_path;
|
|
if (window.url_query_params.has('path'))
|
|
predefined_path = window.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) {
|
|
if (!window.url_query_params.has('c')) {
|
|
let posargs = undefined;
|
|
if (window.app_query_params && window.app_query_params.posargs) {
|
|
posargs = JSON.parse(window.app_query_params.posargs);
|
|
}
|
|
launch_app({
|
|
app: window.app_launched_from_url.name,
|
|
app_obj: window.app_launched_from_url,
|
|
readURL: window.url_query_params.get('readURL'),
|
|
maximized: window.url_query_params.get('maximized'),
|
|
params: window.app_query_params ?? [],
|
|
...(posargs ? {
|
|
args: {
|
|
command_line: { args: posargs }
|
|
}
|
|
} : {}),
|
|
is_fullpage: window.is_fullpage_mode,
|
|
window_options: {
|
|
stay_on_top: false,
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
$(el_desktop).on('mousedown touchstart', { passive: true }, 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');
|
|
// hide all global menubars
|
|
$('.window-menubar-global').hide();
|
|
})
|
|
|
|
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.toLocaleString('default', { month: 'short' });
|
|
|
|
var dt = x.getDate().toString();
|
|
dt = dt.length == 1 ? 0 + dt : dt;
|
|
|
|
var day = x.toLocaleString('default', { weekday: 'short' });
|
|
|
|
|
|
var x1 = day + ", " + month + " " + dt;
|
|
x1 = hours + ":" + minutes + ampm + " " + x1;
|
|
$('#clock').html(x1);
|
|
}
|
|
display_ct()
|
|
setInterval(display_ct, 1000);
|
|
|
|
// show referral notice window
|
|
if (window.show_referral_notice && !window.user.email_confirmed) {
|
|
puter.kv.get('shown_referral_notice').then(async (val) => {
|
|
if (!val || val === 'false' || val === false) {
|
|
setTimeout(() => {
|
|
UIWindowClaimReferral();
|
|
}, 1000);
|
|
puter.kv.set({
|
|
key: "shown_referral_notice",
|
|
value: true,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
window.hide_toolbar = (animate = true) => {
|
|
// Always show toolbar on mobile and tablet devices
|
|
if (isMobile.phone || isMobile.tablet) {
|
|
return;
|
|
}
|
|
|
|
// Don't hide toolbar if auto-hide is disabled
|
|
if (!window.toolbar_auto_hide_enabled) {
|
|
return;
|
|
}
|
|
|
|
if ($('.toolbar').hasClass('toolbar-hidden')) return;
|
|
|
|
// attach hidden class to toolbar
|
|
$('.toolbar').addClass('toolbar-hidden');
|
|
|
|
// animate the toolbar to top = -20px;
|
|
// animate width to 40px;
|
|
if (animate) {
|
|
$('.toolbar').animate({
|
|
top: '-20px',
|
|
width: '40px',
|
|
}, 100);
|
|
} else {
|
|
$('.toolbar').css({
|
|
top: '-20px',
|
|
width: '40px',
|
|
});
|
|
}
|
|
// animate hide toolbar-btn, toolbar-clock
|
|
if (animate) {
|
|
$('.toolbar-btn, #clock, .user-options-menu-btn').animate({
|
|
opacity: 0,
|
|
}, 10);
|
|
} else {
|
|
$('.toolbar-btn, #clock, .user-options-menu-btn').css({
|
|
opacity: 0,
|
|
});
|
|
}
|
|
|
|
if(!window.has_seen_toolbar_animation){
|
|
puter.kv.set({
|
|
key: "has_seen_toolbar_animation",
|
|
value: true,
|
|
})
|
|
|
|
window.has_seen_toolbar_animation = true;
|
|
}
|
|
}
|
|
|
|
window.show_toolbar = () => {
|
|
if (!$('.toolbar').hasClass('toolbar-hidden')) return;
|
|
|
|
// remove hidden class from toolbar
|
|
$('.toolbar').removeClass('toolbar-hidden');
|
|
|
|
$('.toolbar').animate({
|
|
top: 0,
|
|
}, 100).css('width', 'max-content');
|
|
|
|
// animate show toolbar-btn, toolbar-clock
|
|
$('.toolbar-btn, #clock, .user-options-menu-btn').animate({
|
|
opacity: 0.8,
|
|
}, 50);
|
|
}
|
|
|
|
// Toolbar hide/show logic with improved UX
|
|
window.toolbarHideTimeout = null;
|
|
let isMouseNearToolbar = false;
|
|
|
|
// Define safe zone around toolbar (in pixels)
|
|
const TOOLBAR_SAFE_ZONE = 30;
|
|
const TOOLBAR_HIDE_DELAY = 100; // Base delay before hiding
|
|
const TOOLBAR_QUICK_HIDE_DELAY = 200; // Quicker hide when mouse moves far away
|
|
|
|
// Function to check if mouse is in the safe zone around toolbar
|
|
window.isMouseInToolbarSafeZone = (mouseX, mouseY) => {
|
|
const toolbar = $('.toolbar')[0];
|
|
if (!toolbar) return false;
|
|
|
|
const rect = toolbar.getBoundingClientRect();
|
|
|
|
// Expand the toolbar bounds by the safe zone
|
|
const safeZone = {
|
|
top: rect.top - TOOLBAR_SAFE_ZONE,
|
|
bottom: rect.bottom + TOOLBAR_SAFE_ZONE,
|
|
left: rect.left - TOOLBAR_SAFE_ZONE,
|
|
right: rect.right + TOOLBAR_SAFE_ZONE
|
|
};
|
|
|
|
return mouseX >= safeZone.left &&
|
|
mouseX <= safeZone.right &&
|
|
mouseY >= safeZone.top &&
|
|
mouseY <= safeZone.bottom;
|
|
};
|
|
|
|
// Function to handle toolbar hiding with improved logic
|
|
window.handleToolbarHiding = (mouseX, mouseY) => {
|
|
// Always show toolbar on mobile and tablet devices
|
|
if (isMobile.phone || isMobile.tablet) {
|
|
return;
|
|
}
|
|
|
|
// Don't hide toolbar if auto-hide is disabled
|
|
if (!window.toolbar_auto_hide_enabled) {
|
|
return;
|
|
}
|
|
|
|
// Clear any existing timeout
|
|
if (window.toolbarHideTimeout) {
|
|
clearTimeout(window.toolbarHideTimeout);
|
|
window.toolbarHideTimeout = null;
|
|
}
|
|
|
|
// Don't hide if toolbar is already hidden
|
|
if ($('.toolbar').hasClass('toolbar-hidden')) return;
|
|
|
|
const wasNearToolbar = isMouseNearToolbar;
|
|
isMouseNearToolbar = window.isMouseInToolbarSafeZone(mouseX, mouseY);
|
|
|
|
// If mouse is in safe zone, don't hide
|
|
if (isMouseNearToolbar) {
|
|
return;
|
|
}
|
|
|
|
// Determine hide delay based on mouse movement pattern
|
|
let hideDelay = TOOLBAR_HIDE_DELAY;
|
|
|
|
// If mouse was previously near toolbar and now moved far away, hide quicker
|
|
if (wasNearToolbar && !isMouseNearToolbar) {
|
|
// Check if mouse moved significantly away
|
|
const toolbar = $('.toolbar')[0];
|
|
if (toolbar) {
|
|
const rect = toolbar.getBoundingClientRect();
|
|
const distanceFromToolbar = Math.min(
|
|
Math.abs(mouseY - rect.bottom),
|
|
Math.abs(mouseY - rect.top)
|
|
);
|
|
|
|
// If mouse is far from toolbar, hide quicker
|
|
if (distanceFromToolbar > TOOLBAR_SAFE_ZONE * 2) {
|
|
hideDelay = TOOLBAR_QUICK_HIDE_DELAY;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set timeout to hide toolbar
|
|
window.toolbarHideTimeout = setTimeout(() => {
|
|
// Double-check mouse position before hiding
|
|
if (!window.isMouseInToolbarSafeZone(window.mouseX, window.mouseY)) {
|
|
window.hide_toolbar();
|
|
}
|
|
window.toolbarHideTimeout = null;
|
|
}, hideDelay);
|
|
};
|
|
|
|
// debounce timer to prevent toolbar showing immediately after drag ends
|
|
window.drag_release_debounce_timer = null;
|
|
const DRAG_RELEASE_DEBOUNCE_MS = 300;
|
|
|
|
// track when drag operations end to enable debounce
|
|
$(document).on('dragend', function() {
|
|
window.drag_release_debounce_timer = setTimeout(() => {
|
|
window.drag_release_debounce_timer = null;
|
|
}, DRAG_RELEASE_DEBOUNCE_MS);
|
|
});
|
|
|
|
// also debounce when mouseup occurs while in drag state
|
|
$(document).on('mouseup', function() {
|
|
if (window.a_window_is_being_dragged || window.an_item_is_being_dragged) {
|
|
window.drag_release_debounce_timer = setTimeout(() => {
|
|
window.drag_release_debounce_timer = null;
|
|
}, DRAG_RELEASE_DEBOUNCE_MS);
|
|
}
|
|
});
|
|
|
|
// hovering over a hidden toolbar will show it
|
|
$(document).on('mouseenter', '.toolbar-hidden', function () {
|
|
// if a window is being dragged or currently in drag release debounce period, don't show the toolbar
|
|
if(window.a_window_is_being_dragged || window.drag_release_debounce_timer !== null)
|
|
return;
|
|
|
|
// if selectable is active , don't show the toolbar
|
|
if(window.desktop_selectable_is_active)
|
|
return;
|
|
|
|
// if an item is being dragged, don't show the toolbar
|
|
if(window.an_item_is_being_dragged)
|
|
return;
|
|
|
|
if(window.is_fullpage_mode)
|
|
$('.window-app-iframe').css('pointer-events', 'none');
|
|
|
|
window.show_toolbar();
|
|
// Clear any pending hide timeout
|
|
if (window.toolbarHideTimeout) {
|
|
clearTimeout(window.toolbarHideTimeout);
|
|
window.toolbarHideTimeout = null;
|
|
}
|
|
});
|
|
|
|
// hovering over a visible toolbar will show it and cancel hiding
|
|
$(document).on('mouseenter', '.toolbar:not(.toolbar-hidden)', function () {
|
|
// if a window is being dragged, don't show the toolbar
|
|
if(window.a_window_is_being_dragged)
|
|
return;
|
|
|
|
// Clear any pending hide timeout when entering toolbar
|
|
if (window.toolbarHideTimeout) {
|
|
clearTimeout(window.toolbarHideTimeout);
|
|
window.toolbarHideTimeout = null;
|
|
}
|
|
isMouseNearToolbar = true;
|
|
});
|
|
|
|
$(document).on('mouseenter', '.toolbar', function () {
|
|
if(window.is_fullpage_mode)
|
|
$('.toolbar').focus();
|
|
});
|
|
|
|
// any click will hide the toolbar, unless:
|
|
// - it's on the toolbar
|
|
// - it's the user options menu button
|
|
// - the user options menu is open
|
|
$(document).on('click', function(e){
|
|
// Always show toolbar on mobile and tablet devices
|
|
if (isMobile.phone || isMobile.tablet) {
|
|
return;
|
|
}
|
|
|
|
// Don't hide toolbar if auto-hide is disabled
|
|
if (!window.toolbar_auto_hide_enabled) {
|
|
return;
|
|
}
|
|
|
|
// if the user has not seen the toolbar animation, don't hide the toolbar
|
|
if(!window.has_seen_toolbar_animation)
|
|
return;
|
|
|
|
if(
|
|
!$(e.target).hasClass('toolbar') &&
|
|
!$(e.target).hasClass('user-options-menu-btn') &&
|
|
$('.context-menu[data-id="user-options-menu"]').length === 0 &&
|
|
true
|
|
){
|
|
window.hide_toolbar(false);
|
|
}
|
|
})
|
|
|
|
// Handle mouse leaving the toolbar
|
|
$(document).on('mouseleave', '.toolbar', function () {
|
|
// Always show toolbar on mobile and tablet devices
|
|
if (isMobile.phone || isMobile.tablet) {
|
|
return;
|
|
}
|
|
|
|
// Don't hide toolbar if auto-hide is disabled
|
|
if (!window.toolbar_auto_hide_enabled) {
|
|
return;
|
|
}
|
|
|
|
window.has_left_toolbar_at_least_once = true;
|
|
// if the user options menu is open, don't hide the toolbar
|
|
if ($('.context-menu[data-id="user-options-menu"]').length > 0)
|
|
return;
|
|
|
|
// Start the hiding logic with current mouse position
|
|
window.handleToolbarHiding(window.mouseX, window.mouseY);
|
|
});
|
|
|
|
// Track mouse movement globally to update toolbar hiding logic
|
|
$(document).on('mousemove', function(e) {
|
|
// Always show toolbar on mobile and tablet devices
|
|
if (isMobile.phone || isMobile.tablet) {
|
|
return;
|
|
}
|
|
|
|
// Don't hide toolbar if auto-hide is disabled
|
|
if (!window.toolbar_auto_hide_enabled) {
|
|
return;
|
|
}
|
|
|
|
// if the user has not seen the toolbar animation, don't hide the toolbar
|
|
if(!window.has_seen_toolbar_animation && !window.has_left_toolbar_at_least_once)
|
|
return;
|
|
|
|
// if the user options menu is open, don't hide the toolbar
|
|
if ($('.context-menu[data-id="user-options-menu"]').length > 0)
|
|
return;
|
|
|
|
// Only handle toolbar hiding if toolbar is visible and mouse moved significantly
|
|
if (!$('.toolbar').hasClass('toolbar-hidden')) {
|
|
// Use throttling to avoid excessive calls
|
|
if (!window.mouseMoveThrottle) {
|
|
window.mouseMoveThrottle = setTimeout(() => {
|
|
window.handleToolbarHiding(window.mouseX, window.mouseY);
|
|
window.mouseMoveThrottle = null;
|
|
}, 100); // Throttle to every 100ms
|
|
}
|
|
}
|
|
});
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
// Trying to view a user's public folder?
|
|
// i.e. https://puter.com/@<username>
|
|
//--------------------------------------------------------------------------------------
|
|
const url_paths = window.location.pathname.split('/').filter(element => element);
|
|
if (window.url_paths[0]?.startsWith('@')) {
|
|
const username = window.url_paths[0].substring(1);
|
|
let item_path = '/' + username + '/Public';
|
|
if ( window.url_paths.length > 1 ) {
|
|
item_path += '/' + window.url_paths.slice(1).join('/');
|
|
}
|
|
|
|
// GUARD: avoid invalid user directories
|
|
{
|
|
if (!username.match(/^[a-z0-9_]+$/i)) {
|
|
UIAlert({
|
|
message: i18n('error_invalid_username')
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
let stat;
|
|
try {
|
|
stat = await puter.fs.stat({path: item_path, consistency: 'eventual'});
|
|
} catch ( e ) {
|
|
window.history.replaceState(null, document.title, '/');
|
|
UIAlert({
|
|
message: i18n('error_user_or_path_not_found'),
|
|
type: 'error'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// TODO: DRY everything here with open_item. Unfortunately we can't
|
|
// use open_item here because it's coupled with UI logic;
|
|
// it requires a UIItem element and cannot operate on a
|
|
// file path on its own.
|
|
if ( ! stat.is_dir ) {
|
|
if ( stat.associated_app ) {
|
|
launch_app({ name: stat.associated_app.name });
|
|
return;
|
|
}
|
|
|
|
const ext_pref =
|
|
window.user_preferences[`default_apps${path.extname(item_path).toLowerCase()}`];
|
|
|
|
if ( ext_pref ) {
|
|
launch_app({
|
|
name: ext_pref,
|
|
file_path: item_path,
|
|
});
|
|
return;
|
|
}
|
|
|
|
|
|
const open_item_meta = await $.ajax({
|
|
url: window.api_origin + "/open_item",
|
|
type: 'POST',
|
|
contentType: "application/json",
|
|
data: JSON.stringify({
|
|
path: item_path,
|
|
}),
|
|
headers: {
|
|
"Authorization": "Bearer "+window.auth_token
|
|
},
|
|
statusCode: {
|
|
401: function () {
|
|
window.logout();
|
|
},
|
|
},
|
|
});
|
|
const suggested_apps = open_item_meta?.suggested_apps ?? await window.suggest_apps_for_fsentry({
|
|
path: item_path
|
|
});
|
|
|
|
// Note: I'm not adding unzipping logic here. We'll wait until
|
|
// we've refactored open_item so that Puter can have a
|
|
// properly-reusable open function.
|
|
if ( suggested_apps.length !== 0 ) {
|
|
launch_app({
|
|
name: suggested_apps[0].name,
|
|
token: open_item_meta.token,
|
|
file_path: item_path,
|
|
app_obj: suggested_apps[0],
|
|
window_title: path.basename(item_path),
|
|
maximized: options.maximized,
|
|
file_signature: open_item_meta.signature,
|
|
custom_path: window.location.pathname,
|
|
});
|
|
return;
|
|
}
|
|
|
|
await UIAlert({
|
|
message: 'Cannot find an app to open this file; ' +
|
|
'opening directory instead.'
|
|
});
|
|
item_path = item_path.split('/').slice(0, -1).join('/')
|
|
}
|
|
|
|
UIWindow({
|
|
path: item_path,
|
|
title: path.basename(item_path),
|
|
icon: await item_icon({ is_dir: true, path: item_path }),
|
|
is_dir: true,
|
|
app: 'explorer',
|
|
});
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
// Direct download link
|
|
// i.e. https://puter.com/?download=<file_url>
|
|
//--------------------------------------------------------------------------------------
|
|
if (window.url_paths.length === 0 && window.url_query_params.has('download')) {
|
|
const url = window.url_query_params.get('download');
|
|
let file_name = url.split('/').pop().split('?')[0];
|
|
|
|
let response = await UIAlert({
|
|
message: i18n('confirm_download_file_to_desktop', file_name),
|
|
type: 'confirm',
|
|
buttons: [
|
|
{ label: i18n('alert_yes'), value: true, type: "primary" },
|
|
{ label: i18n('alert_no'), value: false, type: "secondary" }
|
|
],
|
|
});
|
|
|
|
if (!response)
|
|
return;
|
|
|
|
|
|
|
|
let cancelled = false;
|
|
let upload_xhr = null;
|
|
const abort_controller = new AbortController();
|
|
|
|
// create progressbar dialog
|
|
let progwin = await UIWindowProgress({
|
|
title: i18n('downloading'),
|
|
icon: window.icons[`app-icon-uploader.svg`],
|
|
operation_id: window.uuidv4(),
|
|
show_progress: true,
|
|
on_cancel: () => {
|
|
cancelled = true;
|
|
abort_controller.abort();
|
|
if (upload_xhr) {
|
|
upload_xhr.abort();
|
|
}
|
|
}
|
|
});
|
|
progwin?.set_status(i18n('downloading_file', file_name));
|
|
|
|
(async () => {
|
|
try {
|
|
// download the file
|
|
const response = await puter.net.fetch(url, {
|
|
signal: abort_controller.signal,
|
|
});
|
|
|
|
const total = Number(response.headers.get('content-length'));
|
|
const reader = response.body.getReader();
|
|
|
|
const chunks = [];
|
|
let received = 0;
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done || cancelled) break;
|
|
|
|
if (value) {
|
|
// store the chunk
|
|
chunks.push(value);
|
|
received += value.length;
|
|
// calculate progress
|
|
const progress = Number.isFinite(total) && total > 0
|
|
? received / total
|
|
: 0;
|
|
// update progressbar
|
|
progwin?.set_progress(Math.floor(progress * 100));
|
|
}
|
|
}
|
|
|
|
if (cancelled) {
|
|
progwin?.close();
|
|
return;
|
|
}
|
|
|
|
// combine chunks into a blob
|
|
let blob = new Blob(chunks, {
|
|
type: response.headers.get('content-type') ?? 'application/octet-stream',
|
|
});
|
|
|
|
// reset progressbar
|
|
progwin?.set_progress(0);
|
|
progwin?.set_status(i18n('uploading_file', file_name));
|
|
|
|
// upload to user's desktop
|
|
await puter.fs.write(`~/Desktop/${file_name}`, blob, {
|
|
dedupeName: true,
|
|
progress: (_, percent) => {
|
|
// update progressbar
|
|
progwin?.set_progress(percent);
|
|
},
|
|
init: (_, xhr) => {
|
|
upload_xhr = xhr;
|
|
}
|
|
});
|
|
} catch (e) {
|
|
// alert the user if there's a genuine error
|
|
if (!cancelled && e.name !== 'AbortError') {
|
|
await UIAlert({
|
|
message: i18n('error_download_failed') + ': ' + e.message,
|
|
type: 'error',
|
|
});
|
|
}
|
|
}
|
|
// close progress window
|
|
progwin?.close();
|
|
})();
|
|
}
|
|
}
|
|
|
|
$(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();
|
|
|
|
// Get current taskbar position
|
|
const currentPosition = window.taskbar_position || 'bottom';
|
|
|
|
// Create base menu items
|
|
let menuItems = [];
|
|
|
|
// Only show position submenu on desktop devices
|
|
if (!isMobile.phone && !isMobile.tablet) {
|
|
menuItems.push({
|
|
html: i18n('desktop_position'),
|
|
items: [
|
|
{
|
|
html: i18n('desktop_position_left'),
|
|
checked: currentPosition === 'left',
|
|
onClick: function() {
|
|
window.update_taskbar_position('left');
|
|
}
|
|
},
|
|
{
|
|
html: i18n('desktop_position_bottom'),
|
|
checked: currentPosition === 'bottom',
|
|
onClick: function() {
|
|
window.update_taskbar_position('bottom');
|
|
}
|
|
},
|
|
{
|
|
html: i18n('desktop_position_right'),
|
|
checked: currentPosition === 'right',
|
|
onClick: function() {
|
|
window.update_taskbar_position('right');
|
|
}
|
|
}
|
|
]
|
|
});
|
|
menuItems.push('-'); // divider
|
|
}
|
|
|
|
// Add the "Show open windows" option for all devices
|
|
menuItems.push({
|
|
html: i18n('desktop_show_open_windows'),
|
|
onClick: function () {
|
|
$(`.window`).showWindow();
|
|
}
|
|
});
|
|
|
|
// Add the "Show the desktop" option for all devices
|
|
menuItems.push({
|
|
html: i18n('desktop_show_desktop'),
|
|
onClick: function () {
|
|
$(`.window`).hideWindow();
|
|
}
|
|
});
|
|
|
|
UIContextMenu({
|
|
parent_element: $('.taskbar'),
|
|
items: menuItems
|
|
});
|
|
return false;
|
|
});
|
|
|
|
// Toolbar context menu
|
|
$(document).on('contextmenu taphold', '.toolbar', function (event) {
|
|
// dismiss taphold on regular devices
|
|
if (event.type === 'taphold' && !isMobile.phone && !isMobile.tablet)
|
|
return;
|
|
|
|
// Don't show context menu on mobile devices since toolbar auto-hide is disabled there
|
|
if (isMobile.phone || isMobile.tablet)
|
|
return;
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
UIContextMenu({
|
|
parent_element: $('.toolbar'),
|
|
items: [
|
|
//--------------------------------------------------
|
|
// Enable/Disable Auto-hide
|
|
//--------------------------------------------------
|
|
{
|
|
html: window.toolbar_auto_hide_enabled ? i18n('Disable Auto-hide') : i18n('Enable Auto-hide'),
|
|
onClick: function () {
|
|
// Toggle the preference
|
|
window.toolbar_auto_hide_enabled = !window.toolbar_auto_hide_enabled;
|
|
|
|
// Save the preference
|
|
puter.kv.set('toolbar_auto_hide_enabled', window.toolbar_auto_hide_enabled.toString());
|
|
|
|
// If auto-hide was just disabled and toolbar is currently hidden, show it
|
|
if (!window.toolbar_auto_hide_enabled && $('.toolbar').hasClass('toolbar-hidden')) {
|
|
window.show_toolbar();
|
|
}
|
|
|
|
// Clear any pending hide timeout
|
|
if (window.toolbarHideTimeout) {
|
|
clearTimeout(window.toolbarHideTimeout);
|
|
window.toolbarHideTimeout = null;
|
|
}
|
|
|
|
// hide toolbar
|
|
window.hide_toolbar();
|
|
}
|
|
}
|
|
]
|
|
});
|
|
return false;
|
|
});
|
|
|
|
$(document).on('click', '.qr-btn', async function (e) {
|
|
UIWindowQR({
|
|
message_i18n_key: 'scan_qr_c2a',
|
|
text: window.gui_origin + '?auth_token=' + window.auth_token,
|
|
});
|
|
})
|
|
|
|
$(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: i18n('save_session'),
|
|
icon: `<svg style="margin-bottom: -4px; width: 16px; height: 16px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px" height="48px" viewBox="0 0 48 48"><g transform="translate(0, 0)"><path d="M45.521,39.04L27.527,5.134c-1.021-1.948-3.427-2.699-5.375-1.679-.717,.376-1.303,.961-1.679,1.679L2.479,39.04c-.676,1.264-.635,2.791,.108,4.017,.716,1.207,2.017,1.946,3.42,1.943H41.993c1.403,.003,2.704-.736,3.42-1.943,.743-1.226,.784-2.753,.108-4.017ZM23.032,15h1.937c.565,0,1.017,.467,1,1.031l-.438,14c-.017,.54-.459,.969-1,.969h-1.062c-.54,0-.983-.429-1-.969l-.438-14c-.018-.564,.435-1.031,1-1.031Zm.968,25c-1.657,0-3-1.343-3-3s1.343-3,3-3,3,1.343,3,3-1.343,3-3,3Z" fill="#ffbb00"></path></g></svg>`,
|
|
icon_active: `<svg style="margin-bottom: -4px; width: 16px; height: 16px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px" height="48px" viewBox="0 0 48 48"><g transform="translate(0, 0)"><path d="M45.521,39.04L27.527,5.134c-1.021-1.948-3.427-2.699-5.375-1.679-.717,.376-1.303,.961-1.679,1.679L2.479,39.04c-.676,1.264-.635,2.791,.108,4.017,.716,1.207,2.017,1.946,3.42,1.943H41.993c1.403,.003,2.704-.736,3.42-1.943,.743-1.226,.784-2.753,.108-4.017ZM23.032,15h1.937c.565,0,1.017,.467,1,1.031l-.438,14c-.017,.54-.459,.969-1,.969h-1.062c-.54,0-.983-.429-1-.969l-.438-14c-.018-.564,.435-1.031,1-1.031Zm.968,25c-1.657,0-3-1.343-3-3s1.343-3,3-3,3,1.343,3,3-1.343,3-3,3Z" fill="#ffbb00"></path></g></svg>`,
|
|
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 === window.user.username ? '✓' : '',
|
|
onClick: async function (val) {
|
|
// don't reload everything if clicked on already-logged-in user
|
|
if (l_user.username === window.user.username)
|
|
return;
|
|
// update auth data
|
|
window.update_auth_data(l_user.auth_token, l_user);
|
|
// refresh
|
|
location.reload();
|
|
}
|
|
|
|
},
|
|
)
|
|
});
|
|
// -------------------------------------------
|
|
// -
|
|
// -------------------------------------------
|
|
items.push('-')
|
|
|
|
items.push(
|
|
{
|
|
html: i18n('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('-')
|
|
|
|
}
|
|
|
|
// -------------------------------------------
|
|
// Load available languages
|
|
// -------------------------------------------
|
|
const supportedLanguagesItems = window.listSupportedLanguages().map(lang => {
|
|
return {
|
|
html: lang.name,
|
|
icon: window.locale === lang.code ? '✓' : '',
|
|
onClick: async function () {
|
|
changeLanguage(lang.code);
|
|
}
|
|
}
|
|
});
|
|
|
|
UIContextMenu({
|
|
id: 'user-options-menu',
|
|
parent_element: parent_element,
|
|
position: { top: pos.top + 28, left: pos.left + pos.width - 15 },
|
|
items: [
|
|
...items,
|
|
//--------------------------------------------------
|
|
// Settings
|
|
//--------------------------------------------------
|
|
{
|
|
html: i18n('settings'),
|
|
id: 'settings',
|
|
onClick: async function () {
|
|
UIWindowSettings();
|
|
}
|
|
},
|
|
//--------------------------------------------------
|
|
// My Websites
|
|
//--------------------------------------------------
|
|
{
|
|
html: i18n('my_websites'),
|
|
id: 'my_websites',
|
|
onClick: async function () {
|
|
UIWindowMyWebsites();
|
|
}
|
|
},
|
|
//--------------------------------------------------
|
|
// Task Manager
|
|
//--------------------------------------------------
|
|
{
|
|
html: i18n('task_manager'),
|
|
id: 'task_manager',
|
|
onClick: async function () {
|
|
UIWindowTaskManager();
|
|
}
|
|
},
|
|
//--------------------------------------------------
|
|
// Contact Us
|
|
//--------------------------------------------------
|
|
{
|
|
html: i18n('contact_us'),
|
|
id: 'contact_us',
|
|
onClick: async function () {
|
|
UIWindowFeedback();
|
|
}
|
|
},
|
|
// -------------------------------------------
|
|
// -
|
|
// -------------------------------------------
|
|
'-',
|
|
|
|
//--------------------------------------------------
|
|
// Log Out
|
|
//--------------------------------------------------
|
|
{
|
|
html: i18n('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: `<p>${i18n('confirm_open_apps_log_out')}</p>`,
|
|
buttons: [
|
|
{
|
|
label: i18n('close_all_windows_and_log_out'),
|
|
value: 'close_and_log_out',
|
|
type: 'primary',
|
|
},
|
|
{
|
|
label: i18n('cancel')
|
|
},
|
|
]
|
|
})
|
|
if (alert_resp === 'close_and_log_out')
|
|
window.logout();
|
|
}
|
|
// no open windows
|
|
else
|
|
window.logout();
|
|
}
|
|
},
|
|
]
|
|
});
|
|
})
|
|
|
|
$(document).on('click', '.fullscreen-btn', async function (e) {
|
|
if (!window.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', '.search-btn', function () {
|
|
UIWindowSearch();
|
|
})
|
|
|
|
$(document).on('click', '.toolbar-puter-logo', function () {
|
|
UIWindowSettings();
|
|
})
|
|
|
|
$(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();
|
|
});
|
|
$(".context-menu").fadeOut(200, function(){
|
|
$(this).remove();
|
|
});
|
|
})
|
|
|
|
$(document).on('click', '.user-options-login-btn', async function (e) {
|
|
const alert_resp = await UIAlert({
|
|
message: `<strong>Save session before exiting!</strong><p>You are in a temporary session and logging into another account will erase all data in your current session.</p>`,
|
|
buttons: [
|
|
{
|
|
label: i18n('save_session'),
|
|
value: 'save-session',
|
|
type: 'primary',
|
|
},
|
|
{
|
|
label: i18n('log_into_another_account_anyway'),
|
|
value: 'login',
|
|
},
|
|
{
|
|
label: i18n('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 window.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();
|
|
window.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', i18n('desktop_exit_full_screen'));
|
|
window.user_preferences.clock_visible === 'auto' && $('#clock').show();
|
|
} else {
|
|
$('.fullscreen-btn').css('background-image', `url(${window.icons['fullscreen.svg']})`);
|
|
$('.fullscreen-btn').attr('title', i18n('desktop_enter_full_screen'));
|
|
window.user_preferences.clock_visible === 'auto' && $('#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: window.api_origin + "/update-taskbar-items",
|
|
type: 'POST',
|
|
data: JSON.stringify({
|
|
items: items,
|
|
}),
|
|
async: true,
|
|
contentType: "application/json",
|
|
headers: {
|
|
"Authorization": "Bearer " + window.auth_token
|
|
},
|
|
})
|
|
}
|
|
|
|
window.remove_taskbar_item = function (item) {
|
|
$(item).find('*').fadeOut(100, function () { });
|
|
|
|
$(item).animate({ width: 0 }, 200, function () {
|
|
$(item).remove();
|
|
|
|
// Adjust taskbar item sizes after removing an item
|
|
if (window.adjust_taskbar_item_sizes) {
|
|
setTimeout(() => {
|
|
window.adjust_taskbar_item_sizes();
|
|
}, 10);
|
|
}
|
|
})
|
|
}
|
|
|
|
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: window.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) {
|
|
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
|
|
window.refresh_desktop_background();
|
|
}
|
|
|
|
window.reset_window_size_and_position = (el_window) => {
|
|
$(el_window).css({
|
|
width: 680,
|
|
height: 380,
|
|
'border-radius': window.window_border_radius,
|
|
top: 'calc(50% - 190px)',
|
|
left: 'calc(50% - 340px)',
|
|
});
|
|
}
|
|
|
|
// Modify the hide/show functions to use CSS rules that will apply to all icons, including future ones
|
|
window.hideDesktopIcons = function () {
|
|
$('.desktop.item-container').addClass('desktop-icons-hidden');
|
|
};
|
|
|
|
window.showDesktopIcons = function () {
|
|
$('.desktop.item-container').removeClass('desktop-icons-hidden');
|
|
};
|
|
|
|
// Add this function to the global scope
|
|
window.toggleDesktopIcons = function () {
|
|
window.desktop_icons_hidden = !window.desktop_icons_hidden;
|
|
|
|
if (window.desktop_icons_hidden) {
|
|
hideDesktopIcons();
|
|
} else {
|
|
showDesktopIcons();
|
|
}
|
|
|
|
// Save preference
|
|
puter.kv.set('desktop_icons_hidden', window.desktop_icons_hidden.toString());
|
|
};
|
|
|
|
$(document).on('click', '.btn-show-ai', function () {
|
|
$('.window[data-app="ai"]').makeWindowVisible();
|
|
});
|
|
|
|
export default UIDesktop; |