Dashboard Update (#2788)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
Notify HeyPuter / notify (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled

* Consistent header with files

* custom order with draggable app icons

* mobile style tweak

* Drag apps to sidebar

* uninstall

* Add desktop switch button to home tab
Drag sidebar apps order

* Display app storage use in Usage tab

* Add context menu options to apps: open in new tab, open in window

* Sidebar apps section with taskbar/dock behavior

* add close button to sidebar app item

* prevent dashboard files items from being selected when dragging

* File contextmenu shows filename
Keep dashboard URL when opening items

* Fixed issue with mobile selecting

* sidebar toggling

* sidebar tooltips

* done!

* Files tab statusbar width fix

* Consistent header with files

* custom order with draggable app icons

* mobile style tweak

* Drag apps to sidebar

* uninstall

* Add desktop switch button to home tab
Drag sidebar apps order

* Display app storage use in Usage tab

* Add context menu options to apps: open in new tab, open in window

* Sidebar apps section with taskbar/dock behavior

* add close button to sidebar app item

* prevent dashboard files items from being selected when dragging

* File contextmenu shows filename
Keep dashboard URL when opening items

* Fixed issue with mobile selecting

* sidebar toggling

* sidebar tooltips

* done!

* Files tab statusbar width fix

* Remove desktop switch button from Home tab

* App uninstall fix

* mobile style tweaks for dashboard sidebar

* Usage tab header fixes

* style tweaks

* Disable Uninstall for certain system apps

* Use app title in uninstall modal

* Remove delete-data option from uninstall modal

* Update dashboard.css

* Update TabApps.js

---------

Co-authored-by: jelveh <nj@puter.com>
This commit is contained in:
Miika Kuisma
2026-04-15 19:46:25 -07:00
committed by GitHub
co-authored by jelveh
parent 90a4970a03
commit 2790879fc1
13 changed files with 831 additions and 304 deletions
+27
View File
@@ -546,6 +546,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.1028.0.tgz",
"integrity": "sha512-OkO2p9Wm+6CccOfQcdYjvCAJdUfBSWbmrIKXGh/qbBjp0B8d1MsYl1Exps5OzRSzqLVuTUVjPJCkgSMJF/mPqg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
@@ -1848,6 +1849,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -2231,6 +2233,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.19.0"
},
@@ -2279,6 +2282,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -2860,6 +2864,7 @@
"resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz",
"integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -4656,6 +4661,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
"integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -4665,6 +4671,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.49.1.tgz",
"integrity": "sha512-kaNl/T7WzyMUQHQlVq7q0oV4Kev6+0xFwqzofryC66jgGMacd0QH5TwfpbUwSTby+SdAdprAe5UKMvBw4tKS5Q==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api": "^1.0.0"
},
@@ -5145,6 +5152,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.40.0.tgz",
"integrity": "sha512-8WRuvGnfnbeR9ifGjLN8kklk2fkd0gBT6aN7NHO9zeYF/6qacAViD3bwAKqGXKnJgl39l1EU41I9diqUjamEEQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api": "^1.0.0"
},
@@ -5287,6 +5295,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.40.0.tgz",
"integrity": "sha512-8WRuvGnfnbeR9ifGjLN8kklk2fkd0gBT6aN7NHO9zeYF/6qacAViD3bwAKqGXKnJgl39l1EU41I9diqUjamEEQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api": "^1.0.0"
},
@@ -5430,6 +5439,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.40.0.tgz",
"integrity": "sha512-8WRuvGnfnbeR9ifGjLN8kklk2fkd0gBT6aN7NHO9zeYF/6qacAViD3bwAKqGXKnJgl39l1EU41I9diqUjamEEQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api": "^1.0.0"
},
@@ -8873,6 +8883,7 @@
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.58.1",
"@typescript-eslint/types": "8.58.1",
@@ -9625,6 +9636,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -10526,6 +10538,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -10747,6 +10760,7 @@
"resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
"integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
"license": "MIT",
"peer": true,
"dependencies": {
"assertion-error": "^1.1.0",
"check-error": "^1.0.3",
@@ -12610,6 +12624,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -13290,6 +13305,7 @@
"resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.5.tgz",
"integrity": "sha512-0DS4Nn4rV8qyFlQCpKK8brT61EUtswynrpfFTcgLErcilBIBskSMQ86fO2WVuybr14ywyKdRjv91FiRZwnEuvQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"readline-sync": "^1.4.10",
"sprintf-js": "^1.1.3",
@@ -15269,6 +15285,7 @@
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
"integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ioredis/commands": "1.5.1",
"cluster-key-slot": "^1.1.0",
@@ -20921,6 +20938,7 @@
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -21568,6 +21586,7 @@
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
"integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "~4.4.1",
"ws": "~8.18.3"
@@ -22938,6 +22957,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -23222,6 +23242,7 @@
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
@@ -23430,6 +23451,7 @@
"integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/expect": "4.1.4",
"@vitest/mocker": "4.1.4",
@@ -23565,6 +23587,7 @@
"integrity": "sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -23614,6 +23637,7 @@
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",
@@ -23991,6 +24015,7 @@
"resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
"integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.8",
@@ -24503,6 +24528,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -24637,6 +24663,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz",
"integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
+4 -4
View File
@@ -915,18 +915,18 @@ let sidebar = [
title: '<code>showSpinner()</code>',
page_title: '<code>puter.ui.showSpinner()</code>',
title_tag: 'puter.ui.showSpinner()',
icon:'/assets/img/function.svg',
icon: '/assets/img/function.svg',
source: '/UI/showSpinner.md',
path: '/UI/showSpinner',
},
{
{
title: '<code>hideSpinner()</code>',
page_title: '<code>puter.ui.hideSpinner()</code>',
title_tag: 'puter.ui.hideSpinner()',
icon:'/assets/img/function.svg',
icon: '/assets/img/function.svg',
source: '/UI/hideSpinner.md',
path: '/UI/hideSpinner',
},
},
{
title: '<code>showWindow()</code>',
page_title: '<code>puter.ui.showWindow()</code>',
@@ -35,8 +35,10 @@ export default class ContextMenuModal {
* Show the modal positioned over a specific element
* @param {Array} menuItems - Array of menu item objects or '-' for separator
* @param {DOMRect} targetRect - Bounding rectangle of the tapped item
* @param {Object} [options] - Optional settings
* @param {string} [options.title] - Title displayed at the top of the menu
*/
show (menuItems, targetRect) {
show (menuItems, targetRect, options = {}) {
if ( this.backdrop ) return; // Already showing
this.menuItems = menuItems;
@@ -50,7 +52,15 @@ export default class ContextMenuModal {
this.modal.className = 'context-menu-modal-dialog';
// Build modal content
let titleHtml = '';
if ( options.title ) {
const titleEl = document.createElement('div');
titleEl.className = 'context-menu-title';
titleEl.textContent = options.title;
titleHtml = titleEl.outerHTML;
}
this.modal.innerHTML = `
${titleHtml}
<div class="context-menu-items">
${this.renderMenuItems(menuItems)}
</div>
+105 -1
View File
@@ -1,3 +1,17 @@
import UIContextMenu from '../UIContextMenu.js';
/** Lowercase app names that must not offer Uninstall in the My Apps tile context menu. */
const APP_NAMES_NO_UNINSTALL = new Set([
'dev-center',
'app-center',
'editor',
'camera',
'recorder',
'memos',
'music-player',
'ai',
]);
/**
* Copyright (C) 2024-present Puter Technologies Inc.
*
@@ -34,7 +48,7 @@ function buildAppsGrid (apps) {
const title = (app.title || app.name || '').trim();
const iconUrl = app.iconUrl || window.icons['app.svg'];
h += `<div class="myapps-tile" data-app-name="${html_encode(app.name)}" title="${html_encode(title)}">`;
h += `<div class="myapps-tile" data-app-name="${html_encode(app.name)}" data-app-title="${html_encode(title)}" data-app-uid="${html_encode(app.uid || '')}" title="${html_encode(title)}">`;
h += '<div class="myapps-tile-icon">';
h += `<img src="${html_encode(iconUrl)}" alt="" draggable="false">`;
h += '</div>';
@@ -45,6 +59,59 @@ function buildAppsGrid (apps) {
return h;
}
function showUninstallModal ({ appName, appTitle, appUid, self, $el_window }) {
const displayName = (appTitle || appName || '').trim();
const $overlay = $(`
<div class="myapps-modal-overlay">
<div class="myapps-modal">
<h3>Uninstall ${html_encode(displayName)}?</h3>
<p>This will revoke all permissions for this app.</p>
<div class="myapps-modal-buttons">
<button class="myapps-modal-btn myapps-modal-cancel">Cancel</button>
<button class="myapps-modal-btn myapps-modal-confirm">Uninstall</button>
</div>
</div>
</div>
`);
$el_window.append($overlay);
const close = () => $overlay.remove();
$overlay.on('click', '.myapps-modal-cancel', close);
$overlay.on('click', function (e) {
if ( e.target === $overlay[0] ) close();
});
$(document).on('keydown.uninstall-modal', function (e) {
if ( e.key === 'Escape' ) {
close();
$(document).off('keydown.uninstall-modal');
}
});
$overlay.on('click', '.myapps-modal-confirm', async function () {
const $btn = $(this);
$btn.prop('disabled', true).text('Uninstalling…');
try {
await puter.perms.revokeApp(appUid, '*');
// Remove from internal state
self._apps = self._apps.filter(a => a.name !== appName);
// Remove tile from DOM
$el_window.find(`.myapps-tile[data-app-name="${appName}"]`).remove();
// Show empty state if no apps left
if ( self._apps.length === 0 ) {
const $container = $el_window.find('.myapps-container');
$container.html(buildAppsGrid(null));
}
} catch ( err ) {
console.error('Failed to uninstall app:', err);
}
close();
$(document).off('keydown.uninstall-modal');
});
}
function revealWhenLoaded ($container) {
const $grid = $container.find('.myapps-grid-loading');
if ( $grid.length === 0 ) return;
@@ -151,6 +218,42 @@ const TabApps = {
window.open(`/app/${appName}`, '_blank');
}
});
// Context menu on right-click
$el_window.on('contextmenu', '.myapps-tile', function (e) {
const appName = $(this).attr('data-app-name');
const appTitle = $(this).attr('data-app-title');
const appUid = $(this).attr('data-app-uid');
const nameLower = (appName || '').toLowerCase();
const noUninstall = APP_NAMES_NO_UNINSTALL.has(nameLower);
const items = noUninstall
? []
: [
{
html: 'Uninstall',
onClick: () => {
showUninstallModal({
appName,
appTitle,
appUid,
self,
$el_window,
});
},
},
];
if ( items.length === 0 ) return;
e.preventDefault();
e.stopPropagation();
UIContextMenu({
position: { top: e.clientY, left: e.clientX },
items,
});
});
},
async loadApps ($el_window) {
@@ -185,6 +288,7 @@ const TabApps = {
].map(app => ({
name: app.name,
title: app.title,
uid: app.uuid || app.uid || null,
iconUrl: app.icon || null,
}));
+48 -141
View File
@@ -27,6 +27,7 @@ import UIAlert from '../UIAlert.js';
import generate_file_context_menu from '../../helpers/generate_file_context_menu.js';
import truncate_filename from '../../helpers/truncate_filename.js';
import update_title_based_on_uploads from '../../helpers/update_title_based_on_uploads.js';
import item_icon from '../../helpers/item_icon.js';
import new_context_menu_item from '../../helpers/new_context_menu_item.js';
import ContextMenuModal from './ContextMenu/ContextMenu.js';
@@ -232,7 +233,8 @@ const TabFiles = {
};
// Add touch-device class for touch devices to show .item-more button
if ( window.isMobile.phone || window.isMobile.tablet ) {
// Use multiple detection methods since user-agent sniffing can miss devices
if ( window.isMobile.phone || window.isMobile.tablet || navigator.maxTouchPoints > 0 ) {
$el_window.find('.files-tab').addClass('touch-device');
}
@@ -1983,7 +1985,8 @@ const TabFiles = {
const is_shared_with_me = (file.path !== `/${window.user.username}` && !file.path.startsWith(`/${window.user.username}/`));
const is_worker = file.workers?.length > 0;
const worker_url = is_worker ? file.workers[0]?.address : '';
const icon = file.is_dir ? `<img src="${html_encode(window.icons['folder.svg'])}"/>` : ((file.thumbnail && this.currentView === 'grid') ? `<img src="${file.thumbnail}" alt="${displayName}" />` : this.determineIcon(file));
const iconResult = await item_icon(file);
const icon = `<img src="${html_encode(iconResult.image)}"/>`;
const row = document.createElement("div");
row.setAttribute('class', `item row ${file.is_dir ? 'folder' : 'file'}`);
row.setAttribute("data-id", item_id);
@@ -2005,7 +2008,7 @@ const TabFiles = {
row.setAttribute("data-size", file.size);
row.setAttribute("data-type", html_encode(file.type) ?? '');
row.setAttribute("data-modified", file.modified);
row.setAttribute("data-associated_app_name", html_encode(file.associated_app_name) ?? '');
row.setAttribute("data-associated_app_name", html_encode(file.associated_app?.name) ?? '');
row.setAttribute("data-path", html_encode(file.path));
row.innerHTML = `
<div class="item-checkbox"><span class="checkbox-icon"></span></div>
@@ -2068,117 +2071,6 @@ const TabFiles = {
this.createItemListeners(row, file);
},
/**
* Determines the appropriate icon for a file based on its extension.
*
* @param {Object} file - The file object containing the filename
* @returns {string} HTML string for the icon image element
*/
determineIcon (file) {
const extension = file.name.split('.').pop().toLowerCase();
switch ( extension ) {
case 'm4a':
case 'ogg':
case 'aac':
case 'flac':
return `<img src="${html_encode(window.icons['file-audio.svg'])}"/>`;
case 'cpp':
return `<img src="${html_encode(window.icons['file-cpp.svg'])}"/>`;
case 'css':
return `<img src="${html_encode(window.icons['file-css.svg'])}"/>`;
case 'csv':
return `<img src="${html_encode(window.icons['file-csv.svg'])}"/>`;
case 'doc':
case 'docx':
return `<img src="${html_encode(window.icons['file-word.svg'])}"/>`;
case 'exe':
return `<img src="${html_encode(window.icons['file-exe.svg'])}"/>`;
case 'gzip':
return `<img src="${html_encode(window.icons['file-gzip.svg'])}"/>`;
case 'html':
return `<img src="${html_encode(window.icons['file-html.svg'])}"/>`;
case 'jpg':
case 'jpeg':
case 'png':
case 'webp':
case 'gif':
return `<img src="${html_encode(window.icons['file-image.svg'])}"/>`;
case 'jar':
return `<img src="${html_encode(window.icons['file-jar.svg'])}"/>`;
case 'java':
return `<img src="${html_encode(window.icons['file-pdf.svg'])}"/>`;
case 'js':
return `<img src="${html_encode(window.icons['file-js.svg'])}"/>`;
case 'json':
return `<img src="${html_encode(window.icons['file-json.svg'])}"/>`;
case 'jsp':
return `<img src="${html_encode(window.icons['file-jsp.svg'])}"/>`;
case 'log':
return `<img src="${html_encode(window.icons['file-log.svg'])}"/>`;
case 'md':
return `<img src="${html_encode(window.icons['file-md.svg'])}"/>`;
case 'mp3':
return `<img src="${html_encode(window.icons['file-mp3.svg'])}"/>`;
case 'otf':
return `<img src="${html_encode(window.icons['file-otf.svg'])}"/>`;
case 'pdf':
return `<img src="${html_encode(window.icons['file-pdf.svg'])}"/>`;
case 'php':
return `<img src="${html_encode(window.icons['file-php.svg'])}"/>`;
case 'pptx':
return `<img src="${html_encode(window.icons['file-pptx.svg'])}"/>`;
case 'psd':
return `<img src="${html_encode(window.icons['file-psd.svg'])}"/>`;
case 'py':
return `<img src="${html_encode(window.icons['file-py.svg'])}"/>`;
case 'rss':
return `<img src="${html_encode(window.icons['file-rss.svg'])}"/>`;
case 'rtf':
return `<img src="${html_encode(window.icons['file-rtf.svg'])}"/>`;
case 'ruby':
return `<img src="${html_encode(window.icons['file-ruby.svg'])}"/>`;
case 'sketch':
return `<img src="${html_encode(window.icons['file-sketch.svg'])}"/>`;
case 'sql':
return `<img src="${html_encode(window.icons['file-sql.svg'])}"/>`;
case 'svg':
return `<img src="${html_encode(window.icons['file-svg.svg'])}"/>`;
case 'tar':
return `<img src="${html_encode(window.icons['file-tar.svg'])}"/>`;
case 'tpl':
case 'xltx':
case 'potx':
case 'tmpl':
return `<img src="${html_encode(window.icons['file-template.svg'])}"/>`;
case 'text':
case 'txt':
return `<img src="${html_encode(window.icons['file-text.svg'])}"/>`;
case 'tif':
return `<img src="${html_encode(window.icons['file-tif.svg'])}"/>`;
case 'tiff':
return `<img src="${html_encode(window.icons['file-tiff.svg'])}"/>`;
case 'ttf':
return `<img src="${html_encode(window.icons['file-ttf.svg'])}"/>`;
case 'mp4':
case 'avi':
case 'mov':
case 'wmf':
case 'mkv':
case 'webm':
return `<img src="${html_encode(window.icons['file-video.svg'])}"/>`;
case 'wav':
return `<img src="${html_encode(window.icons['file-wav.svg'])}"/>`;
case 'xlsx':
return `<img src="${html_encode(window.icons['file-xlsx.svg'])}"/>`;
case 'xml':
return `<img src="${html_encode(window.icons['file-xml.svg'])}"/>`;
case 'zip':
return `<img src="${html_encode(window.icons['file-zip.svg'])}"/>`;
default:
return `<img src="${html_encode(window.icons['file.svg'])}"/>`;
}
},
/**
* Attaches event listeners to a file/folder row element.
*
@@ -2199,11 +2091,20 @@ const TabFiles = {
let rename_cancelled = false;
let shift_clicked = false;
let itemWasSelectedOnMousedown = false;
let lastPointerType = null;
el_item.onpointerdown = (e) => {
if ( e.target.classList.contains('item-more') ) return;
if ( el_item.classList.contains('header') ) return;
// Track pointer type so onclick can distinguish touch from mouse.
lastPointerType = e.pointerType;
// On touch devices, skip all selection logic here.
// Taps are handled by onclick (opens item) and taphold (context menu),
// so pointerdown never accidentally selects while the user is scrolling.
if ( e.pointerType === 'touch' ) return;
shift_clicked = false;
// Track whether item was already selected before this mousedown
@@ -2251,7 +2152,9 @@ const TabFiles = {
// In select mode on mobile, treat taps like Ctrl+click (toggle selection)
const isMobileSelectMode = (window.isMobile.phone || window.isMobile.tablet) && _this.selectModeActive;
// If clicking on .item-name, .item-icon, or .item-badges, select immediately so item drag works
// If clicking on .item-name, .item-icon, or .item-badges, select immediately so item drag works.
// On touch devices, these elements have pointer-events:none via CSS so this path
// won't be reached — touches land on .row instead, deferring selection to onclick.
const isDragHandle = e.target.closest('.item-name, .item-icon, .item-badges');
if ( e.button === 0 && !e.ctrlKey && !e.metaKey && !e.shiftKey && !el_item.classList.contains('selected') && !isMobileSelectMode && isDragHandle ) {
el_item.parentElement.querySelectorAll('.row.selected').forEach(r => {
@@ -2324,9 +2227,23 @@ const TabFiles = {
return;
}
// On mobile in select mode, selection was already handled in pointerdown
// Just return early to prevent any further processing
if ( (window.isMobile.phone || window.isMobile.tablet) && _this.selectModeActive ) {
// On touch/mobile: tap opens the item directly, no selection step needed.
// Selection (and context menu) is handled via taphold.
// Use lastPointerType as primary signal (works even if isMobile misdetects).
if ( lastPointerType === 'touch' || window.isMobile.phone || window.isMobile.tablet ) {
// In select mode, tap toggles selection instead of opening
if ( _this.selectModeActive ) {
el_item.classList.toggle('selected');
window.latest_selected_item = el_item;
_this.updateFooterStats();
return;
}
if ( isFolder === "1" ) {
_this.pushNavHistory(file.path);
_this.renderDirectory(file.path);
} else {
open_item({ item: el_item });
}
return;
}
@@ -2353,18 +2270,6 @@ const TabFiles = {
}
}
}
// On mobile, single tap opens folders (no double-tap on touch devices)
if ( window.isMobile.phone || window.isMobile.tablet ) {
// Normal mode: open the item
if ( isFolder === "1" ) {
_this.pushNavHistory(file.path);
_this.renderDirectory(file.path);
} else {
open_item({ item: el_item });
}
el_item.classList.remove('selected');
}
};
el_item.ondblclick = (e) => {
@@ -2470,9 +2375,16 @@ const TabFiles = {
// Right-click context menu handler (desktop) and taphold (touch devices)
$(el_item).on('contextmenu taphold', async (e) => {
// Dismiss taphold on non-touch devices
if ( e.type === 'taphold' && !window.isMobile.phone && !window.isMobile.tablet ) {
if ( e.type === 'taphold' && !window.isMobile.phone && !window.isMobile.tablet && !(navigator.maxTouchPoints > 0) ) {
return;
}
// On iOS, both contextmenu and taphold can fire for the same long-press.
// Debounce to prevent duplicate modals.
if ( el_item._contextMenuShownAt && Date.now() - el_item._contextMenuShownAt < 500 ) {
e.preventDefault();
return;
}
el_item._contextMenuShownAt = Date.now();
e.preventDefault();
e.stopPropagation();
@@ -2484,9 +2396,9 @@ const TabFiles = {
items = await _this.generateContextMenuItems(el_item, file);
}
if ( window.isMobile.phone || window.isMobile.tablet ) {
if ( window.isMobile.phone || window.isMobile.tablet || navigator.maxTouchPoints > 0 ) {
const modal = new ContextMenuModal();
modal.show(items, el_item.getBoundingClientRect());
modal.show(items, el_item.getBoundingClientRect(), { title: file.name });
} else {
UIContextMenu({ items: items, position: { left: e.pageX, top: e.pageY } });
}
@@ -3140,12 +3052,7 @@ const TabFiles = {
* @returns {void}
*/
updateDashboardUrl (filePath) {
// Use pushState to update URL without firing hashchange.
// The popstate listener in UIDashboard handles back/forward navigation.
const newHash = `#files${filePath}`;
if ( window.location.hash !== newHash ) {
history.pushState(null, '', newHash);
}
// Disabled: don't modify the browser URL when navigating files.
},
/**
@@ -3169,10 +3076,10 @@ const TabFiles = {
}
// Use mobile-friendly context menu on touch devices
if ( window.isMobile.phone || window.isMobile.tablet ) {
if ( window.isMobile.phone || window.isMobile.tablet || navigator.maxTouchPoints > 0 ) {
const targetRect = targetElement.getBoundingClientRect();
const modal = new ContextMenuModal();
modal.show(items, targetRect);
modal.show(items, targetRect, { title: file.name });
} else {
UIContextMenu({ items: items });
}
+13
View File
@@ -166,6 +166,14 @@ const TabHome = {
h += '</div>';
h += '</div>';
// Desktop switch card (spans full width)
// h += '<div class="bento-card bento-desktop-switch">';
// h += '<div class="bento-desktop-switch-inner">';
// h += '<span class="bento-desktop-switch-text">Looking for Puter\'s desktop interface?</span>';
// h += '<button class="bento-desktop-switch-btn">Switch to Desktop</button>';
// h += '</div>';
// h += '</div>';
// Usage card (spans full width on second row)
h += '<div class="bento-card bento-usage">';
h += '<a href="#" class="bento-card-fancy-header" data-target-tab="usage">';
@@ -217,6 +225,11 @@ const TabHome = {
}
});
// Handle desktop switch button
$el_window.on('click', '.bento-desktop-switch-btn', function () {
window.location.href = '/';
});
// Handle "Save Account" warning click
$el_window.on('click', '.bento-save-account-warning', function (e) {
e.preventDefault();
+18 -14
View File
@@ -20,7 +20,7 @@
// Sort state for the usage table
let usageTableSortState = {
column: 'cost', // default sort by cost
direction: 'desc' // default descending (highest cost first)
direction: 'desc', // default descending (highest cost first)
};
let usageTableData = []; // Store raw data for sorting
let usageTableExpanded = false; // Track if table is showing all rows
@@ -29,10 +29,13 @@ const USAGE_TABLE_INITIAL_ROWS = 10;
const TabUsage = {
id: 'usage',
label: 'Usage',
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-speedometer2" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4M3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707M2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10m9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5m.754-4.246a.39.39 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.39.39 0 0 0-.029-.518z"/> <path fill-rule="evenodd" d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A8 8 0 0 1 0 10m8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3"/> </svg>`,
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-speedometer2" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4M3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707M2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10m9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5m.754-4.246a.39.39 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.39.39 0 0 0-.029-.518z"/> <path fill-rule="evenodd" d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A8 8 0 0 1 0 10m8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3"/> </svg>',
html: () => {
return `
<h1>${i18n('usage')}<button class="update-usage-details" style="float:right;"><svg class="update-usage-details-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z"/> <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466"/> </svg></button></h1>
<div class="dashboard-section-header">
<h2>${i18n('usage')}</h2>
<p>Puter resources and AI usage</p>
</div>
<div class="driver-usage">
<div class="driver-usage-header">
<h3 style="margin:0; font-size: 14px; flex-grow: 1; font-weight: 500;">${i18n('Storage')}</h3>
@@ -77,7 +80,7 @@ const TabUsage = {
// Click handler for sortable table headers
$($el_window).on('click', '.driver-usage-details-content-table th[data-sort]', function () {
const column = $(this).data('sort');
// Toggle direction if same column, otherwise default to descending
if ( usageTableSortState.column === column ) {
usageTableSortState.direction = usageTableSortState.direction === 'asc' ? 'desc' : 'asc';
@@ -103,11 +106,11 @@ const TabUsage = {
},
};
function getSortIcon(column) {
function getSortIcon (column) {
const isActive = usageTableSortState.column === column;
const direction = usageTableSortState.direction;
if ( !isActive ) {
if ( ! isActive ) {
// Neutral sort icon (both arrows, dimmed)
return `<span class="sort-icon sort-icon-neutral">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
@@ -131,11 +134,11 @@ function getSortIcon(column) {
}
}
function renderUsageTable() {
function renderUsageTable () {
// Sort the data
const sortedData = [...usageTableData].sort((a, b) => {
let aVal, bVal;
switch ( usageTableSortState.column ) {
case 'resource':
aVal = a.resource.toLowerCase();
@@ -147,7 +150,7 @@ function renderUsageTable() {
bVal = b.rawCost;
break;
}
if ( aVal < bVal ) return usageTableSortState.direction === 'asc' ? -1 : 1;
if ( aVal > bVal ) return usageTableSortState.direction === 'asc' ? 1 : -1;
return 0;
@@ -244,7 +247,7 @@ async function update_usage_details ($el_window) {
rawUnits: rawUnits,
formattedUnits: formattedUnits,
rawCost: rawCost,
formattedCost: window.number_format(rawCost / 100_000_000, { decimals: 2, prefix: '$' })
formattedCost: window.number_format(rawCost / 100_000_000, { decimals: 2, prefix: '$' }),
});
}
@@ -269,9 +272,10 @@ async function update_usage_details ($el_window) {
$('#storage-used').html(window.byte_format(general_used));
$('#storage-capacity').html(window.byte_format(res.capacity));
$('#storage-used-percent').html(
`${usage_percentage }%${
host_usage_percentage > 0
? ` / ${ host_usage_percentage }%` : ''}`);
`${usage_percentage }%${
host_usage_percentage > 0
? ` / ${ host_usage_percentage }%` : ''}`,
);
$('#storage-bar').css('width', `${usage_percentage }%`);
$('#storage-bar-host').css('width', `${host_usage_percentage }%`);
if ( usage_percentage >= 100 ) {
+39 -4
View File
@@ -25,7 +25,6 @@ import UIAlert from '../UIAlert.js';
import UIWindowSaveAccount from '../UIWindowSaveAccount.js';
import UIWindowLogin from '../UIWindowLogin.js';
import UIWindowFeedback from '../UIWindowFeedback.js';
/**
* Creates and displays the Dashboard window.
*
@@ -83,6 +82,14 @@ async function UIDashboard (options) {
// Sidebar
h += '<div class="dashboard-sidebar hide-scrollbar">';
// Sidebar header with logo and collapse toggle
h += '<div class="dashboard-sidebar-header">';
h += `<div class="dashboard-sidebar-logo"><img src="${window.icons['logo.svg']}" alt="Puter"><span>Puter</span></div>`;
h += '<button class="dashboard-sidebar-collapse-toggle">';
h += `<img class="sidebar-toggle-close" src="${window.icons['sidebar-close.svg']}">`;
h += `<img class="sidebar-toggle-open" src="${window.icons['sidebar-open.svg']}">`;
h += '</button>';
h += '</div>';
// Navigation items container
h += '<div class="dashboard-sidebar-nav">';
for ( let i = 0; i < tabs.length; i++ ) {
@@ -93,7 +100,7 @@ async function UIDashboard (options) {
}
const isActive = i === 0 ? ' active' : '';
const isBeta = tab.label === 'Apps';
h += `<div class="dashboard-sidebar-item${isActive} ${isBeta ? 'beta' : ''}" data-section="${tab.id}">`;
h += `<div class="dashboard-sidebar-item${isActive} ${isBeta ? 'beta' : ''}" data-section="${tab.id}" data-tooltip="${html_encode(tab.label)}">`;
h += tab.icon;
h += tab.label;
h += '</div>';
@@ -101,8 +108,8 @@ async function UIDashboard (options) {
h += '</div>';
// User options button at bottom
h += '<div class="dashboard-user-options hide-scrollbar">';
h += '<div class="dashboard-user-btn hide-scrollbar">';
h += '<div class="dashboard-user-options">';
h += '<div class="dashboard-user-btn">';
h += `<div class="dashboard-user-avatar profile-pic" style="background-image: url(${window.user?.profile?.picture || window.icons['profile.svg']})"></div>`;
h += `<span class="dashboard-user-name">${window.html_encode(window.user?.username || 'User')}</span>`;
h += '<svg class="dashboard-user-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>';
@@ -143,6 +150,11 @@ async function UIDashboard (options) {
const $el_window = $(el_window);
// Restore sidebar collapsed state
if ( localStorage.getItem('dashboard-sidebar-collapsed') === '1' ) {
$el_window.find('.dashboard-sidebar').addClass('collapsed');
}
// Set initial file path BEFORE tabs are initialized (so TabFiles.init() can use it)
if ( window.dashboard_initial_route?.tab === 'files' && window.dashboard_initial_route?.path ) {
window.dashboard_initial_file_path = window.dashboard_initial_route.path;
@@ -416,6 +428,29 @@ async function UIDashboard (options) {
$el_window.find('.dashboard-sidebar').toggleClass('open');
});
// Desktop sidebar collapse toggle
$el_window.on('click', '.dashboard-sidebar-collapse-toggle', function () {
const $sidebar = $el_window.find('.dashboard-sidebar');
$sidebar.toggleClass('collapsed');
localStorage.setItem('dashboard-sidebar-collapsed', $sidebar.hasClass('collapsed') ? '1' : '0');
});
// Sidebar collapsed tooltips
const $tooltip = $('<div class="dashboard-sidebar-tooltip"></div>').appendTo('body');
$el_window.on('mouseenter', '.dashboard-sidebar-item[data-tooltip]', function () {
if ( ! $el_window.find('.dashboard-sidebar').hasClass('collapsed') ) return;
const rect = this.getBoundingClientRect();
$tooltip.text($(this).attr('data-tooltip'));
$tooltip.css({
top: rect.top + rect.height / 2 - $tooltip.outerHeight() / 2,
left: rect.right + 8,
});
$tooltip.addClass('visible');
});
$el_window.on('mouseleave', '.dashboard-sidebar-item[data-tooltip]', function () {
$tooltip.removeClass('visible');
});
// Close sidebar when clicking outside
$el_window.on('mousedown touchstart', function (e) {
if ( !$(e.target).closest('.dashboard-sidebar').length
+15 -13
View File
@@ -4080,24 +4080,26 @@ $.fn.focusWindow = function (event) {
window.window_stack.push(parseInt($(this).attr('data-id')));
// remove blurred class from items on this window
$(window.active_item_container).find('.item-blurred').removeClass('item-blurred');
//change window URL
const update_window_url = $(this).attr('data-update_window_url');
const url_app_name = $(this).attr('data-app_pseudonym') || $(this).attr('data-app');
let custom_path = $(this).attr('data-custom_path');
//change window URL (skip in dashboard mode — URL should stay on the dashboard route)
if ( ! window.is_dashboard_mode ) {
const update_window_url = $(this).attr('data-update_window_url');
const url_app_name = $(this).attr('data-app_pseudonym') || $(this).attr('data-app');
let custom_path = $(this).attr('data-custom_path');
if ( custom_path && custom_path !== '' ) {
if ( update_window_url === 'true' || update_window_url === null ) {
if ( ! custom_path.startsWith('/') ) {
custom_path = `/${ custom_path}`;
if ( custom_path && custom_path !== '' ) {
if ( update_window_url === 'true' || update_window_url === null ) {
if ( ! custom_path.startsWith('/') ) {
custom_path = `/${ custom_path}`;
}
window.history.replaceState({ window_id: $(this).attr('data-id') }, '', custom_path);
document.title = $(this).attr('data-name');
}
window.history.replaceState({ window_id: $(this).attr('data-id') }, '', custom_path);
}
else if ( update_window_url === 'true' || update_window_url === null ) {
window.history.replaceState({ window_id: $(this).attr('data-id') }, '', `/app/${url_app_name}${$(this).attr('data-user_set_url_params')}`);
document.title = $(this).attr('data-name');
}
}
else if ( update_window_url === 'true' || update_window_url === null ) {
window.history.replaceState({ window_id: $(this).attr('data-id') }, '', `/app/${url_app_name}${$(this).attr('data-user_set_url_params')}`);
document.title = $(this).attr('data-name');
}
$(`.taskbar .taskbar-item[data-app="${$(this).attr('data-app')}"]`).addClass('taskbar-item-active');
} else {
$('.window').find('.item-selected').addClass('item-blurred');
+520 -112
View File
@@ -165,6 +165,11 @@ body {
}
}
.window-dashboard ~ .window {
box-shadow: none;
border-radius: 0;
}
.dashboard {
display: flex;
height: 100%;
@@ -180,16 +185,164 @@ body {
display: flex;
flex-direction: column;
box-sizing: border-box;
transition: width 0.2s ease, min-width 0.2s ease, padding 0.2s ease;
}
/* Sidebar header with logo and collapse toggle */
.dashboard-sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 0px 10px 5px;
flex-shrink: 0;
border-bottom: 1px solid var(--dashboard-border);
margin-bottom: 10px;
}
.dashboard-sidebar-logo {
display: flex;
gap: 10px;
align-items: center;
font-size: 14px;
color: var(--dashboard-text);
}
.dashboard-sidebar-logo img {
height: 24px;
width: auto;
opacity: 1;
transition: opacity 0.15s ease;
}
.dashboard-sidebar-collapse-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
background: none;
border-radius: 6px;
cursor: pointer;
color: var(--dashboard-text-muted);
transition: background-color 0.15s, color 0.15s;
flex-shrink: 0;
}
.dashboard-sidebar-collapse-toggle img {
width: 24px;
height: 24px;
opacity: 0.6;
}
.dashboard-sidebar-collapse-toggle .sidebar-toggle-open {
display: none;
}
.dashboard-sidebar-collapse-toggle:hover {
background: var(--dashboard-hover);
color: var(--dashboard-text);
}
/* Collapsed sidebar state */
.dashboard-sidebar.collapsed {
width: 50px;
min-width: 50px;
padding: 16px 6px;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-header {
justify-content: center;
padding: 4px 0 12px;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-logo {
display: none;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-collapse-toggle .sidebar-toggle-close {
display: none;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-collapse-toggle .sidebar-toggle-open {
display: block;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-item {
justify-content: center;
padding: 10px;
gap: 0;
overflow: hidden;
font-size: 0;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-item svg {
flex-shrink: 0;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-separator {
margin: 8px 4px;
}
.dashboard-sidebar.collapsed .dashboard-sidebar-item.beta::after {
display: none;
}
.dashboard-sidebar.collapsed .dashboard-user-options {
padding-top: 8px;
}
.dashboard-sidebar.collapsed .dashboard-user-btn {
justify-content: center;
padding: 10px;
gap: 0;
}
.dashboard-sidebar.collapsed .dashboard-user-name,
.dashboard-sidebar.collapsed .dashboard-user-chevron {
display: none;
}
/* Tooltip for collapsed sidebar */
.dashboard-sidebar-tooltip {
position: fixed;
padding: 4px 10px;
background: var(--dashboard-sidebar-background);
border: 1px solid var(--dashboard-border);
border-radius: 6px;
font-size: 13px;
color: var(--dashboard-text);
white-space: nowrap;
pointer-events: none;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
opacity: 0;
transition: opacity 0.15s ease;
}
.dashboard-sidebar-tooltip.visible {
opacity: 1;
}
/* Hide desktop collapse toggle on mobile */
@media (max-width: 768px) {
.dashboard-sidebar-collapse-toggle {
display: none;
}
}
.dashboard-sidebar-nav {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.dashboard-sidebar-separator {
border: none;
border-top: 1px solid var(--dashboard-border);
margin: 8px 16px;
flex-shrink: 0;
}
.dashboard-sidebar-item {
@@ -204,6 +357,7 @@ body {
font-size: 14px;
color: var(--dashboard-text);
transition: background-color 0.15s;
flex-shrink: 0;
}
.dashboard-sidebar-item svg {
@@ -212,8 +366,10 @@ body {
flex-shrink: 0;
}
.dashboard-sidebar-item:hover {
background: var(--dashboard-hover);
@media (hover: hover) {
.dashboard-sidebar-item:hover {
background: var(--dashboard-hover);
}
}
.dashboard-sidebar-item.active {
@@ -249,8 +405,10 @@ body {
transition: background-color 0.15s;
}
.dashboard-user-btn:hover {
background: var(--dashboard-hover);
@media (hover: hover) {
.dashboard-user-btn:hover {
background: var(--dashboard-hover);
}
}
.dashboard-user-btn.has-open-contextmenu {
@@ -315,7 +473,6 @@ body {
}
.dashboard-section-apps {
max-width: 800px;
margin: 0 auto;
}
@@ -360,6 +517,12 @@ body {
}
/* Dashboard Apps */
.dashboard-content.apps {
padding: 0;
}
.dashboard-apps-container {
margin-top: 8px;
}
@@ -390,12 +553,16 @@ body {
cursor: pointer;
width: 90px;
}
.dashboard-app-card:hover {
background: none !important;
@media (hover: hover) {
.dashboard-app-card:hover {
background: none !important;
}
}
.dashboard-app-card:hover .start-app, .dashboard-app-card .start-app:hover {
background: none !important;
@media (hover: hover) {
.dashboard-app-card:hover .start-app, .dashboard-app-card .start-app:hover {
background: none !important;
}
}
.dashboard-app-card:active {
@@ -419,8 +586,10 @@ body {
.dashboard-app-card .start-app{
transition: transform 0.15s ease;
}
.dashboard-app-card .start-app:hover {
transform: scale(1.05);
@media (hover: hover) {
.dashboard-app-card .start-app:hover {
transform: scale(1.05);
}
}
.dashboard-app-title {
@@ -453,6 +622,7 @@ body {
max-width: 360px;
margin-left: auto;
margin-right: auto;
margin-top: 30px;
}
.myapps-search-icon {
@@ -471,21 +641,22 @@ body {
cursor: pointer;
}
.myapps-icon-clear:hover {
color: var(--dashboard-text-primary);
@media (hover: hover) {
.myapps-icon-clear:hover {
color: var(--dashboard-text-primary);
}
}
.myapps-search {
width: 100%;
padding: 8px 36px 8px 12px;
font-size: 14px;
padding-right: 33px !important;
font-size: 13px;
border: 1px solid var(--dashboard-border);
border-radius: 8px;
border-radius: 6px;
background: var(--dashboard-input-background);
color: var(--dashboard-text-primary);
outline: none;
transition: border-color 0.15s;
padding-right: 35px;
}
.myapps-search:focus {
@@ -561,8 +732,10 @@ body {
object-fit: cover;
}
.myapps-tile:hover .myapps-tile-icon {
transform: scale(1.08);
@media (hover: hover) {
.myapps-tile:hover .myapps-tile-icon {
transform: scale(1.08);
}
}
.myapps-tile:active .myapps-tile-icon {
@@ -582,6 +755,99 @@ body {
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
white-space: nowrap;
}
/* Uninstall confirmation modal */
.myapps-modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 300;
}
.myapps-modal {
background: var(--dashboard-card-background);
border: 1px solid var(--dashboard-border);
border-radius: 12px;
padding: 24px;
min-width: 320px;
max-width: 400px;
box-shadow: 0 8px 32px var(--dashboard-shadow-medium);
}
.myapps-modal h3 {
margin: 0 0 8px;
font-size: 16px;
font-weight: 600;
color: var(--dashboard-text-primary);
}
.myapps-modal p {
margin: 0 0 16px;
font-size: 14px;
color: var(--dashboard-text-secondary);
}
.myapps-modal-buttons {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.myapps-modal-btn {
padding: 8px 16px;
border-radius: 8px;
border: 1px solid var(--dashboard-border);
font-size: 14px;
cursor: pointer;
transition: background 0.15s, opacity 0.15s;
}
.myapps-modal-cancel {
background: var(--dashboard-card-background);
color: var(--dashboard-text-primary);
}
@media (hover: hover) {
.myapps-modal-cancel:hover {
background: var(--dashboard-hover);
}
}
.myapps-modal-confirm {
background: #e53935;
color: #fff;
border-color: #e53935;
}
@media (hover: hover) {
.myapps-modal-confirm:hover {
background: #c62828;
border-color: #c62828;
}
}
.myapps-modal-confirm:disabled {
opacity: 0.6;
cursor: default;
}
/* Custom sort drag-and-drop */
.myapps-grid-custom .myapps-tile {
cursor: grab;
}
.myapps-grid-custom .myapps-tile:active {
cursor: grabbing;
}
.myapps-tile-placeholder {
width: 80px;
border: 2px dashed var(--dashboard-border);
border-radius: 12px;
background: var(--dashboard-hover);
opacity: 0.5;
}
.ui-sortable-helper.myapps-tile {
transform: scale(1.05);
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
z-index: 1000;
opacity: 0.9;
}
/* Dashboard files */
@@ -636,8 +902,10 @@ body {
transition: background-color 0.15s;
}
.dashboard-section-files .directories li:hover {
background: var(--dashboard-hover);
@media (hover: hover) {
.dashboard-section-files .directories li:hover {
background: var(--dashboard-hover);
}
}
.dashboard-section-files .directories li.context-menu-active {
@@ -672,7 +940,6 @@ body {
.dashboard-section-files .directory-contents .header {
position: sticky;
top: 0;
height: 94px;
padding-top: 12px;
background: var(--dashboard-background);
}
@@ -707,20 +974,24 @@ body {
background-color: transparent;
}
.dashboard .path-btn:hover {
filter: invert(1) hue-rotate(180deg);
background-color: var(--select-color);
opacity: 1;
@media (hover: hover) {
.dashboard .path-btn:hover {
filter: invert(1) hue-rotate(180deg);
background-color: var(--select-color);
opacity: 1;
}
}
@media (prefers-color-scheme: dark) {
.path-btn {
filter: invert(1) hue-rotate(180deg);
}
.path-btn:hover {
filter: invert(0) hue-rotate(0deg);
background-color: var(--select-color);
opacity: 1;
@media (hover: hover) {
.path-btn:hover {
filter: invert(0) hue-rotate(0deg);
background-color: var(--select-color);
opacity: 1;
}
}
}
@@ -752,9 +1023,11 @@ body {
justify-content: center;
}
.dashboard-section-files .header .path-action-btn:hover {
color: var(--dashboard-background);
background-color: var(--select-color);
@media (hover: hover) {
.dashboard-section-files .header .path-action-btn:hover {
color: var(--dashboard-background);
background-color: var(--select-color);
}
}
.dashboard-section-files .header .path-btn-disabled {
@@ -780,10 +1053,12 @@ body {
border-radius: 6px;
}
.dashboard-section-files .header .path .dirname:hover {
color: var(--dashboard-background);
background-color: var(--select-color);
border: 1px solid var(--select-color);
@media (hover: hover) {
.dashboard-section-files .header .path .dirname:hover {
color: var(--dashboard-background);
background-color: var(--select-color);
border: 1px solid var(--select-color);
}
}
.dashboard-section-files .header .path .dirname.drop-target {
@@ -833,15 +1108,19 @@ body {
margin: 1px 2px;
pointer-events: auto;
float: unset;
-webkit-user-select: none;
user-select: none;
}
.dashboard-section-files .row.folder {
border: 1px solid transparent;
}
.dashboard-section-files .row:hover {
background: var(--dashboard-hover);
border-radius: 3px;
@media (hover: hover) {
.dashboard-section-files .row:hover {
background: var(--dashboard-hover);
border-radius: 3px;
}
}
.dashboard-section-files .row.selected {
@@ -896,6 +1175,15 @@ body {
line-height: 32px;
}
/* On touch devices, item-name/icon/badges must not be direct event targets
so that pointerdown's isDragHandle check can't match them and accidentally
select items when the user is scrolling. Events pass through to the .row. */
.files-tab.touch-device .row:not(.header) .item-name,
.files-tab.touch-device .row:not(.header) .item-icon,
.files-tab.touch-device .row:not(.header) .item-badges {
pointer-events: none;
}
.dashboard-section-files .row textarea {
align-items: center;
width: 100%;
@@ -932,8 +1220,10 @@ body {
pointer-events: none;
}
.dashboard-section-files .row:hover .item-more {
color: var(--dashboard-text);
@media (hover: hover) {
.dashboard-section-files .row:hover .item-more {
color: var(--dashboard-text);
}
}
.dashboard-section-files.ui-draggable-dragging {
@@ -1140,10 +1430,10 @@ body {
}
.dashboard-section-files .files-footer {
position: fixed;
position: absolute;
bottom: 0;
right: 0;
left: 371px;
left: 0;
background: linear-gradient(180deg, var(--dashboard-sidebar-background), var(--dashboard-background));
border-top: 1px solid var(--dashboard-border);
height: 30px;
@@ -1216,8 +1506,10 @@ body {
transition: background-color 0.15s ease, color 0.15s ease;
}
.dashboard-section-files .selection-action-btn:hover {
background: var(--dashboard-hover);
@media (hover: hover) {
.dashboard-section-files .selection-action-btn:hover {
background: var(--dashboard-hover);
}
}
.dashboard-section-files .selection-action-btn:active {
@@ -1234,16 +1526,20 @@ body {
color: #43a047;
}
.dashboard-section-files .selection-action-btn.restore-btn:hover {
background: rgba(67, 160, 71, 0.1);
@media (hover: hover) {
.dashboard-section-files .selection-action-btn.restore-btn:hover {
background: rgba(67, 160, 71, 0.1);
}
}
.dashboard-section-files .selection-action-btn.delete-btn {
color: #e53935;
}
.dashboard-section-files .selection-action-btn.delete-btn:hover {
background: rgba(229, 57, 53, 0.1);
@media (hover: hover) {
.dashboard-section-files .selection-action-btn.delete-btn:hover {
background: rgba(229, 57, 53, 0.1);
}
}
/* Select mode button - hidden on desktop by default */
@@ -1382,9 +1678,11 @@ body {
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
}
.dashboard-section-files .files-tab .files.files-grid-view .row:hover {
background: var(--dashboard-sidebar-background);
border-color: var(--dashboard-border);
@media (hover: hover) {
.dashboard-section-files .files-tab .files.files-grid-view .row:hover {
background: var(--dashboard-sidebar-background);
border-color: var(--dashboard-border);
}
}
.dashboard-section-files .files-tab .files.files-grid-view .row.selected {
@@ -1443,26 +1741,30 @@ body {
display: block;
}
.dashboard-section-files .files-tab .files.files-grid-view .row:hover .item-more {
position: absolute;
top: 1px;
right: 1px;
color: #666;
background: var(--dashboard-sidebar-background);
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 7px;
@media (hover: hover) {
.dashboard-section-files .files-tab .files.files-grid-view .row:hover .item-more {
position: absolute;
top: 1px;
right: 1px;
color: #666;
background: var(--dashboard-sidebar-background);
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 7px;
}
}
/* Hide .item-more on desktop (non-touch devices) - use right-click context menu instead */
.dashboard-section-files .files-tab:not(.touch-device) .files.files-list-view .row .item-more,
.dashboard-section-files .files-tab:not(.touch-device) .files.files-grid-view .row .item-more,
.dashboard-section-files .files-tab:not(.touch-device) .files.files-grid-view .row:hover .item-more,
.dashboard-section-files .files-tab:not(.touch-device) .header .columns .item-more {
display: none !important;
@media (hover: hover) {
.dashboard-section-files .files-tab:not(.touch-device) .files.files-grid-view .row:hover .item-more,
.dashboard-section-files .files-tab:not(.touch-device) .header .columns .item-more {
display: none !important;
}
}
.dashboard-section-files .files-tab .files.files-grid-view .row .item-name-editor {
@@ -1485,8 +1787,10 @@ body {
justify-content: space-between;
}
.dashboard-section-files .header .columns .sortable:hover {
color: var(--dashboard-text-heading);
@media (hover: hover) {
.dashboard-section-files .header .columns .sortable:hover {
color: var(--dashboard-text-heading);
}
}
.dashboard-section-files .header .columns .sortable::after {
@@ -1523,8 +1827,10 @@ body {
background: var(--dashboard-sidebar-background);
}
.dashboard-section-files .header .columns .col-resize-handle:hover {
background: var(--dashboard-border);
@media (hover: hover) {
.dashboard-section-files .header .columns .col-resize-handle:hover {
background: var(--dashboard-border);
}
}
.dashboard-section-files .header .columns .col-resize-handle:active {
@@ -1573,8 +1879,10 @@ body {
text-align: left;
}
.dashboard-section-files .more-menu .menu-item:hover {
background: var(--dashboard-hover);
@media (hover: hover) {
.dashboard-section-files .more-menu .menu-item:hover {
background: var(--dashboard-hover);
}
}
.dashboard-section-files .more-menu .menu-item svg {
@@ -1602,8 +1910,10 @@ body {
color: #ea4335;
}
.dashboard-section-files .more-menu .menu-item.danger:hover {
background: rgba(234, 67, 53, 0.1);
@media (hover: hover) {
.dashboard-section-files .more-menu .menu-item.danger:hover {
background: rgba(234, 67, 53, 0.1);
}
}
.dashboard-section-files .more-menu .menu-divider {
@@ -1619,8 +1929,8 @@ body {
top: 12px;
left: 12px;
z-index: 100;
width: 40px;
height: 40px;
width: 35px;
height: 35px;
background: var(--dashboard-background);
border: 1px solid var(--dashboard-border);
border-radius: 6px;
@@ -1660,9 +1970,17 @@ body {
/* Responsive: tablet and below */
@media (max-width: 768px) {
.dashboard-sidebar-nav {
padding-top: 45px;
.dashboard-sidebar-header {
padding-left: 45px;
border-bottom: 0;
margin-bottom: 2px;
margin-top: 3px;
}
.dashboard-sidebar-header .dashboard-sidebar-logo {
margin-left: auto;
}
.dashboard-sidebar-toggle {
display: flex;
}
@@ -1686,6 +2004,11 @@ body {
padding: 64px 16px 24px;
}
.myapps-search-wrap {
margin-top: 60px;
margin-bottom: 20px;
}
.dashboard-apps-grid {
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 4px;
@@ -1700,8 +2023,8 @@ body {
height: 40px;
}
.myapps-tab {
padding: 20px 16px;
.myapps-container {
padding: 20px 10px;
}
.myapps-tab h2 {
@@ -1876,11 +2199,13 @@ body {
color: var(--dashboard-text-muted);
}
.dashboard-section-files .files-tab .files.files-list-view .row:hover .item-modified,
.dashboard-section-files .files-tab .files.files-list-view .row:hover .item-size,
.dashboard-section-files .files-tab .files.files-list-view .row.selected .item-modified,
.dashboard-section-files .files-tab .files.files-list-view .row.selected .item-size {
/* color: var(--primary-color-sidebar-item); */
@media (hover: hover) {
.dashboard-section-files .files-tab .files.files-list-view .row:hover .item-modified,
.dashboard-section-files .files-tab .files.files-list-view .row:hover .item-size,
.dashboard-section-files .files-tab .files.files-list-view .row.selected .item-modified,
.dashboard-section-files .files-tab .files.files-list-view .row.selected .item-size {
/* color: var(--primary-color-sidebar-item); */
}
}
/* Bullet separator between size and modified */
@@ -2122,10 +2447,12 @@ body {
transition: all 0.15s ease;
}
.dashboard .bento-save-account-warning:hover {
background-color: var(--dashboard-warning-hover-bg);
border-color: var(--dashboard-warning-hover-border);
color: var(--dashboard-warning-hover-text);
@media (hover: hover) {
.dashboard .bento-save-account-warning:hover {
background-color: var(--dashboard-warning-hover-bg);
border-color: var(--dashboard-warning-hover-border);
color: var(--dashboard-warning-hover-text);
}
}
.dashboard .bento-save-account-warning svg {
@@ -2133,8 +2460,10 @@ body {
color: var(--dashboard-warning-icon);
}
.dashboard .bento-save-account-warning:hover svg {
color: var(--dashboard-warning-hover-border);
@media (hover: hover) {
.dashboard .bento-save-account-warning:hover svg {
color: var(--dashboard-warning-hover-border);
}
}
/* Recent Apps Card - Rectangle */
@@ -2237,9 +2566,11 @@ body {
transition: color 0.15s ease;
}
.dashboard .bento-view-more:hover {
color: var(--dashboard-link-hover);
text-decoration: underline;
@media (hover: hover) {
.dashboard .bento-view-more:hover {
color: var(--dashboard-link-hover);
text-decoration: underline;
}
}
.dashboard .bento-recent-apps-container {
@@ -2280,8 +2611,10 @@ body {
width: 200px;
}
.dashboard .bento-recent-app:hover {
transform: scale(1.02);
@media (hover: hover) {
.dashboard .bento-recent-app:hover {
transform: scale(1.02);
}
}
.dashboard .bento-recent-app:active {
@@ -2338,6 +2671,50 @@ body {
color: var(--dashboard-text-tertiary);
}
/* Desktop switch bento card */
.dashboard .bento-desktop-switch {
grid-column: 1 / -1;
min-height: auto;
background: radial-gradient(circle at 90% 10%, var(--dashboard-gradient-indigo) 0%, transparent 40%),
radial-gradient(circle at 10% 90%, var(--dashboard-gradient-pink) 0%, transparent 35%),
linear-gradient(135deg, var(--dashboard-card-gradient-start) 0%, var(--dashboard-card-gradient-end) 100%);
padding: 16px 24px;
}
.dashboard .bento-desktop-switch-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.dashboard .bento-desktop-switch-text {
font-size: 14px;
font-weight: 500;
color: var(--dashboard-text-secondary);
}
.dashboard .bento-desktop-switch-btn {
flex-shrink: 0;
padding: 8px 16px;
font-size: 13px;
font-weight: 500;
color: var(--dashboard-background);
background: var(--dashboard-icon-blue-start);
border: 1px solid var(--dashboard-primary);
border-radius: 8px;
cursor: pointer;
text-decoration: none;
transition: background 0.15s, color 0.15s;
}
@media (hover: hover) {
.dashboard .bento-desktop-switch-btn:hover {
background: var(--dashboard-icon-blue-end);
color: white;
}
}
/* Usage bento card */
.dashboard .bento-usage {
grid-column: 1 / -1;
@@ -2388,9 +2765,11 @@ body {
transition: color 0.2s;
}
.dashboard .bento-plan-upgrade:hover {
color: var(--dashboard-link-hover);
text-decoration: underline;
@media (hover: hover) {
.dashboard .bento-plan-upgrade:hover {
color: var(--dashboard-link-hover);
text-decoration: underline;
}
}
.dashboard .bento-usage-section-header {
@@ -2430,8 +2809,10 @@ body {
transition: opacity 0.2s;
}
.dashboard .bento-usage-card-header:hover {
opacity: 0.7;
@media (hover: hover) {
.dashboard .bento-usage-card-header:hover {
opacity: 0.7;
}
}
.dashboard .bento-usage-card-header h3 {
@@ -2548,6 +2929,10 @@ body {
font-size: 12px;
}
.driver-usage-header {
flex-direction: column;
}
.dashboard .bento-usage-grid {
grid-template-columns: 1fr;
gap: 16px;
@@ -2665,8 +3050,10 @@ body {
box-shadow: 0 4px 16px var(--dashboard-shadow-medium);
}
.dashboard-profile-avatar:hover {
transform: scale(1.05);
@media (hover: hover) {
.dashboard-profile-avatar:hover {
transform: scale(1.05);
}
}
.dashboard-profile-avatar-overlay {
@@ -2681,8 +3068,10 @@ body {
transition: opacity 0.2s ease;
}
.dashboard-profile-avatar:hover .dashboard-profile-avatar-overlay {
opacity: 1;
@media (hover: hover) {
.dashboard-profile-avatar:hover .dashboard-profile-avatar-overlay {
opacity: 1;
}
}
.dashboard-profile-avatar-overlay svg {
@@ -2892,6 +3281,7 @@ body {
.dashboard-danger-card .button {
width: 100%;
}
}
/* ======================================
@@ -2938,11 +3328,25 @@ body {
opacity: 1;
}
/* Title shown at top of context menu (e.g. filename) */
.context-menu-modal-dialog .context-menu-title {
padding: 10px 14px 6px;
font-size: 0.75rem;
font-weight: 600;
color: var(--dashboard-text-muted, #888);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-bottom: 1px solid var(--dashboard-border, #e0e0e0);
flex-shrink: 0;
}
/* Menu items container */
.context-menu-modal-dialog .context-menu-items {
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-y: auto;
}
/* Individual menu item */
@@ -2962,8 +3366,10 @@ body {
border-bottom: none;
}
.context-menu-modal-dialog .context-menu-item:hover {
background-color: var(--dashboard-hover, #e8e8e8);
@media (hover: hover) {
.context-menu-modal-dialog .context-menu-item:hover {
background-color: var(--dashboard-hover, #e8e8e8);
}
}
.context-menu-modal-dialog .context-menu-item:active {
@@ -2999,9 +3405,11 @@ body {
}
}
.context-menu-modal-dialog .context-menu-item:hover .context-menu-item-icon img,
.context-menu-modal-dialog .context-menu-item:hover .context-menu-item-icon svg {
opacity: 1;
@media (hover: hover) {
.context-menu-modal-dialog .context-menu-item:hover .context-menu-item-icon img,
.context-menu-modal-dialog .context-menu-item:hover .context-menu-item-icon svg {
opacity: 1;
}
}
/* Menu item label */
@@ -363,7 +363,7 @@ const generate_file_context_menu = async function (options) {
// Set as Wallpaper
// -------------------------------------------
const mime_type = mime.getType($(el_item).attr('data-name')) ?? 'application/octet-stream';
if ( !window.dashboard_object && !is_trashed && !is_trash && !fsentry.is_dir && mime_type.startsWith('image/') ) {
if ( !is_trashed && !is_trash && !fsentry.is_dir && mime_type.startsWith('image/') ) {
menu_items.push({
html: i18n('set_as_background'),
onClick: async function () {
@@ -372,6 +372,17 @@ const generate_file_context_menu = async function (options) {
url: read_url.items.read_url,
fit: window.desktop_bg_fit,
});
// Update dashboard desktop view background if visible
const $desktopView = $('.files.desktop-view');
if ( $desktopView.length ) {
$desktopView.css({
'background-image': `url(${read_url.items.read_url})`,
'background-size': (window.desktop_bg_fit === 'repeat') ? 'auto' : (window.desktop_bg_fit || 'cover'),
'background-repeat': (window.desktop_bg_fit === 'repeat') ? 'repeat' : 'no-repeat',
'background-position': 'center center',
'background-color': '',
});
}
try {
$.ajax({
url: `${window.api_origin}/set-desktop-bg`,
@@ -534,12 +545,14 @@ const generate_file_context_menu = async function (options) {
if ( is_shared_with_me ) base_dir = window.desktop_path;
window.create_shortcut(path.basename($(el_item).attr('data-path')),
fsentry.is_dir,
base_dir,
null, // appendTo - will be determined by create_shortcut
fsentry.shortcut_to === '' ? fsentry.uid : fsentry.shortcut_to,
fsentry.shortcut_to_path === '' ? fsentry.path : fsentry.shortcut_to_path);
window.create_shortcut(
path.basename($(el_item).attr('data-path')),
fsentry.is_dir,
base_dir,
null, // appendTo - will be determined by create_shortcut
fsentry.shortcut_to === '' ? fsentry.uid : fsentry.shortcut_to,
fsentry.shortcut_to_path === '' ? fsentry.path : fsentry.shortcut_to_path,
);
},
});
}
@@ -626,13 +639,15 @@ const generate_file_context_menu = async function (options) {
let top = $(el_item).position().top + $(el_item).height();
top = top > (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height)) ? (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height)) : top;
UIWindowItemProperties($(el_item).attr('data-name'),
$(el_item).attr('data-path'),
$(el_item).attr('data-uid'),
left,
top,
window_width,
window_height);
UIWindowItemProperties(
$(el_item).attr('data-name'),
$(el_item).attr('data-path'),
$(el_item).attr('data-uid'),
left,
top,
window_width,
window_height,
);
},
});
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z"/></svg>

After

Width:  |  Height:  |  Size: 337 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M500-640v320l160-160-160-160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z"/></svg>

After

Width:  |  Height:  |  Size: 336 B