mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-18 03:57:17 +00:00
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
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:
Generated
+27
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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 |
@@ -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 |
Reference in New Issue
Block a user