Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
546 changes: 546 additions & 0 deletions doc/scratch.md

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions src/gui/src/UI/Settings/UITabAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export default {
h += `<div style="overflow: hidden; display: flex; margin-bottom: 20px; flex-direction: column; align-items: center;">`;
h += `<div class="profile-picture change-profile-picture" style="background-image: url('${html_encode(window.user?.profile?.picture ?? window.icons['profile.svg'])}');">`;
h += `</div>`;
// show remove button only if user has a profile picture
if(window.user?.profile?.picture) {
h += `<button class="button remove-profile-picture" style="margin-top: 10px;">${i18n('remove_profile_picture')}</button>`;
}
h += `</div>`;

// change password button
Expand Down Expand Up @@ -150,6 +154,24 @@ export default {
});
})

$el_window.find('.remove-profile-picture').on('click', function (e) {
console.log('Removing profile picture...');

// Clear the profile picture from user's profile
update_profile(window.user.username, {picture: null});

// Update the profile picture display to default avatar
const defaultAvatar = window.icons['profile.svg'];
$el_window.find('.profile-picture').css('background-image', `url('${defaultAvatar}')`);
$('.profile-image').css('background-image', `url('${defaultAvatar}')`);
$('.profile-image').removeClass('profile-image-has-picture');

// Show the remove button (if hidden)
$el_window.find('.remove-profile-picture').show();

console.log('Profile picture removed successfully');
});

$el_window.on('file_opened', async function(e){
let selected_file = Array.isArray(e.detail) ? e.detail[0] : e.detail;
// set profile picture
Expand Down
23 changes: 23 additions & 0 deletions src/gui/src/UI/Settings/UITabPersonalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export default {
<option value="show">${i18n('clock_visible_show')}</option>
</select>
</div>
<div class="settings-card">
<label style="display: flex; align-items: center;">
<input type="checkbox" class="toolbar-auto-hide-toggle" style="margin-right: 10px;">
<div style="flex-grow: 1;">
<strong>${i18n('toolbar_auto_hide')}</strong>
<p style="margin-top: 5px; margin-bottom: 0; font-size: 12px; opacity: 0.8;">${i18n('toolbar_auto_hide_description')}</p>
</div>
</label>
</div>
<div class="settings-card" style="display: block; height: auto;">
<strong style="margin: 15px 0 30px; display: block;">${i18n('menubar_style')}</strong>
<div style="flex-grow:1; margin-top: 10px;">
Expand Down Expand Up @@ -102,6 +111,20 @@ export default {

window.change_clock_visible();

// Load and set toolbar auto-hide preference
const currentValue = window.user_preferences?.toolbar_auto_hide || false;
$el_window.find('.toolbar-auto-hide-toggle').prop('checked', currentValue);

// Handle toolbar auto-hide toggle change
$el_window.find('.toolbar-auto-hide-toggle').on('change', function(e) {
const isEnabled = $(this).prop('checked');
window.mutate_user_preferences({
toolbar_auto_hide: isEnabled
});
// Reload page to apply changes (since auto-hide is initialized on page load)
window.location.reload();
});

puter.kv.get('menubar_style').then(async (val) => {
if(val === 'system' || !val){
$el_window.find('#menubar_style_system').prop('checked', true);
Expand Down
50 changes: 50 additions & 0 deletions src/gui/src/UI/UIDesktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -706,10 +706,14 @@ async function UIDesktop(options){
}

// update local user preferences
const toolbar_auto_hide_value = await puter.kv.get('user_preferences.toolbar_auto_hide');
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'),
toolbar_auto_hide: toolbar_auto_hide_value !== null && toolbar_auto_hide_value !== undefined
? JSON.parse(toolbar_auto_hide_value)
: false,
};

// update default apps
Expand All @@ -719,6 +723,52 @@ async function UIDesktop(options){
}

window.update_user_preferences(user_preferences);

// Initialize toolbar auto-hide after preferences are loaded
if (user_preferences.toolbar_auto_hide) {
const toolbar = $('.toolbar');
let hideTimeout;
const HIDE_DELAY = 2000; // 2 seconds of inactivity
const SHOW_THRESHOLD = 50; // 50px from top to show toolbar

function hideToolbar() {
toolbar.addClass('toolbar-auto-hide-hidden');
}

function showToolbar() {
toolbar.removeClass('toolbar-auto-hide-hidden');
clearTimeout(hideTimeout);
}

function resetHideTimeout() {
clearTimeout(hideTimeout);
hideTimeout = setTimeout(hideToolbar, HIDE_DELAY);
}

// Show toolbar when mouse enters toolbar area
toolbar.on('mouseenter', function() {
showToolbar();
});

// Track ALL mouse movement for inactivity detection
$(document).on('mousemove', function(e) {
// Always show toolbar when mouse is near top edge
if (e.clientY <= SHOW_THRESHOLD) {
showToolbar();
} else {
// Reset timeout on any mouse movement (inactivity detection)
resetHideTimeout();
}
});

// Reset timeout on mouse click anywhere
$(document).on('mousedown click', function() {
resetHideTimeout();
});

// Initial hide after 2 seconds of inactivity
hideTimeout = setTimeout(hideToolbar, HIDE_DELAY);
}
});

// Append to <body>
Expand Down
95 changes: 95 additions & 0 deletions src/gui/src/UI/UIItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,88 @@ import truncate_filename from '../helpers/truncate_filename.js';
import launch_app from "../helpers/launch_app.js"
import open_item from "../helpers/open_item.js"

/**
* Checks if a file is an image based on MIME type or file extension
* @param {Object} options - File options object
* @param {boolean} options.is_dir - Whether the item is a directory
* @param {string} options.type - MIME type of the file
* @param {string} options.name - Name of the file
* @returns {boolean} True if the file is an image
*/
function is_image_file(options) {
// Directories are not images
if (options.is_dir) return false;

// Check MIME type first (primary method)
if (options.type && options.type.startsWith('image/')) {
return true;
}

// Fallback: check file extension
const ext = path.extname(options.name || '').toLowerCase();
const image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.tiff', '.tif', '.ico'];
return image_extensions.includes(ext);
}

/**
* Sets the desktop background from a file and persists it to the server
* @param {string} file_path - Path to the file
* @param {string} file_uid - UID of the file
* @returns {Promise<void>}
*/
async function set_desktop_background_from_file(file_path, file_uid) {
try {
// Get file signature with read_url
// Use host_app_uid if available, otherwise undefined (backend handles this)
const app_uid = window.host_app_uid || undefined;
const file_signature = await puter.fs.sign(app_uid, {
uid: file_uid,
action: 'read'
});

// Handle both single item and array response
const signature = Array.isArray(file_signature?.items)
? file_signature.items[0]
: (file_signature?.items || file_signature);

const read_url = signature?.read_url;

if (!read_url) {
throw new Error('Could not get read URL for file');
}

// Set desktop background immediately
window.set_desktop_background({
url: read_url,
fit: 'cover'
});

// Persist to server
await $.ajax({
url: window.api_origin + "/set-desktop-bg",
type: 'POST',
data: JSON.stringify({
url: read_url,
fit: 'cover',
color: null
}),
async: true,
contentType: "application/json",
headers: {
"Authorization": "Bearer " + window.auth_token
},
statusCode: {
401: function () {
window.logout();
}
}
});
} catch (err) {
console.error('Failed to set desktop background:', err);
UIAlert('Failed to set desktop background. Please try again.');
}
}

function UIItem(options){
const matching_appendto_count = $(options.appendTo).length;
if(matching_appendto_count > 1){
Expand Down Expand Up @@ -1184,6 +1266,19 @@ function UIItem(options){
});
}
// -------------------------------------------
// Set as Desktop Background
// -------------------------------------------
if(!is_trash && !is_trashed && !options.is_dir && is_image_file(options)){
menu_items.push({
html: i18n('set_as_desktop_background'),
onClick: async function(){
const file_uid = $(el_item).attr('data-uid');
const file_path = $(el_item).attr('data-path');
await set_desktop_background_from_file(file_path, file_uid);
}
});
}
// -------------------------------------------
// Zip
// -------------------------------------------
if(!is_trash && !is_trashed && !$(el_item).attr('data-path').endsWith('.zip')){
Expand Down
8 changes: 6 additions & 2 deletions src/gui/src/UI/UIWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -1609,7 +1609,9 @@ async function UIWindow(options) {
// --------------------------------------------------------
// Close button
// --------------------------------------------------------
$(`#window-${win_id} > .window-head > .window-close-btn`).click(function () {
$(`#window-${win_id} > .window-head > .window-close-btn`).click(function (event) {
event.stopPropagation();
event.preventDefault();
$(el_window).close({
shrink_to_target: options.on_close_shrink_to_target
});
Expand All @@ -1618,7 +1620,9 @@ async function UIWindow(options) {
// --------------------------------------------------------
// Minimize button
// --------------------------------------------------------
$(`#window-${win_id} > .window-head > .window-minimize-btn`).click(function () {
$(`#window-${win_id} > .window-head > .window-minimize-btn`).click(function (event) {
event.stopPropagation();
event.preventDefault();
$(el_window).hideWindow();
})

Expand Down
13 changes: 10 additions & 3 deletions src/gui/src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1218,7 +1218,7 @@ span.header-sort-icon img {
margin: 0;
font-weight: bold;
font-size: 13px;
color: #8f96a3;
color: var(--window-sidebar-title-color, #8f96a3);
text-shadow: 1px 1px rgb(247 247 247 / 15%);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
Expand All @@ -1243,7 +1243,7 @@ span.header-sort-icon img {
margin-top: 2px;
padding: 4px;
border-radius: 3px;
color: #444444;
color: var(--window-sidebar-item-color, #444444);
font-size: 13px;
cursor: pointer;
transition: 0.15s background-color;
Expand Down Expand Up @@ -1748,7 +1748,14 @@ label {
justify-content: flex-end;
align-content: center;
flex-wrap: wrap;
padding-right: 10px
padding-right: 10px;
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}

.toolbar-auto-hide-hidden {
transform: translateY(-100%);
opacity: 0;
pointer-events: none;
}

.show-desktop-btn {
Expand Down
1 change: 1 addition & 0 deletions src/gui/src/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ if (window.user_preferences === null) {
show_hidden_files: false,
language: navigator.language.split("-")[0] || navigator.userLanguage || 'en',
clock_visible: 'auto',
toolbar_auto_hide: false,
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/gui/src/i18n/translations/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ const en = {
refresh: 'Refresh',
release_address_confirmation: `Are you sure you want to release this address?`,
remove_from_taskbar:'Remove from Taskbar',
remove_profile_picture: 'Remove Profile Picture',
rename: 'Rename',
repeat: 'Repeat',
replace: 'Replace',
Expand Down Expand Up @@ -269,8 +270,11 @@ const en = {
settings: "Settings",
set_new_password: "Set New Password",
share: "Share",
toolbar_auto_hide: "Auto-hide Toolbar",
toolbar_auto_hide_description: "Automatically hide the toolbar after 2 seconds of inactivity. Move mouse near top edge to show it again.",
share_to: "Share to",
share_with: "Share with:",
set_as_desktop_background: "Set as Desktop Background",
shortcut_to: "Shortcut to",
show_all_windows: "Show All Windows",
show_hidden: 'Show hidden',
Expand Down
7 changes: 7 additions & 0 deletions src/gui/src/initgui.js
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,13 @@ window.initgui = async function(options){
if($(e.target).hasClass('taskbar') || $(e.target).closest('.taskbar').length > 0)
return;

// if close or minimize button is clicked, don't activate window
// This prevents the window from coming to foreground when closing/minimizing
if($(e.target).hasClass('window-close-btn') || $(e.target).closest('.window-close-btn').length > 0)
return;
if($(e.target).hasClass('window-minimize-btn') || $(e.target).closest('.window-minimize-btn').length > 0)
return;

// if mouse is clicked on a window, activate it
if(window.mouseover_window !== undefined){
// if popover clicked on, don't activate window. This is because if an app
Expand Down
Loading