Compare commits

...
Author SHA1 Message Date
Nariman Jelveh a299872bcc Remove code paths that only ran for the desktop's item container
The desktop is no longer an item container, so the Trash item that
refresh_item_container added to it, the empty-folder message exemption,
and the context menu parent check for it can't be reached.
2026-11-12 12:31:06 -07:00
Nariman Jelveh d374cdb04f Redirect /desktop and /settings to the dashboard
/desktop redirects to the root with its query intact, and /settings to the
dashboard's Home tab. /desktop drops out of the OIDC return_to allowlist,
sign-out returns to the root, and the auth cover page's logo links there.

Also removes eight GUI globals nothing references.
2026-11-12 12:22:28 -07:00
Nariman Jelveh 2d13b73b04 Remove the desktop's icons and right-click menu
The desktop no longer lists the Desktop folder's items, and right-clicking
its background opens no menu. It stays as the surface windows open on.

Also removes what only the icons used: file drops and item drops on the
desktop, rubber-band selection, auto arrange and saved icon positions, and
their two keys in puter.js's GUI boot cache. Clicking the desktop clears the
active item container so keyboard shortcuts stop acting on a window's items.
2026-11-12 12:16:58 -07:00
67 changed files with 100 additions and 797 deletions

No files matched your search

@@ -222,16 +222,8 @@ describe('HomepageController shell routes', () => {
expect(String(captured.body)).toMatch(/<!DOCTYPE html>/i);
});
it('serves the shell on /settings, /dashboard, /desktop, /action', async () => {
for (const path of [
'/settings',
'/settings/*splat',
'/dashboard',
'/dashboard/',
'/desktop',
'/desktop/',
'/action/*splat',
]) {
it('serves the shell on /dashboard and /action', async () => {
for (const path of ['/dashboard', '/dashboard/', '/action/*splat']) {
const { res, captured } = makeRes();
await callRoute('get', path, makeReq({ path }), res);
expect(typeof captured.body).toBe('string');
@@ -239,6 +231,35 @@ describe('HomepageController shell routes', () => {
}
});
it('redirects /desktop to the root, keeping the query', async () => {
for (const path of ['/desktop', '/desktop/']) {
const { res, captured } = makeRes();
await callRoute(
'get',
path,
makeReq({ path, originalUrl: `${path}?ref=abc&x=1` }),
res,
);
expect(captured.statusCode).toBe(302);
expect(captured.location).toBe('/?ref=abc&x=1');
expect(captured.body).toBeUndefined();
}
const { res, captured } = makeRes();
await callRoute('get', '/desktop', makeReq({ path: '/desktop' }), res);
expect(captured.location).toBe('/');
});
it('redirects /settings to the dashboard Home tab', async () => {
for (const path of ['/settings', '/settings/*splat']) {
const { res, captured } = makeRes();
await callRoute('get', path, makeReq({ path }), res);
expect(captured.statusCode).toBe(302);
expect(captured.location).toBe('/dashboard#home');
expect(captured.body).toBeUndefined();
}
});
it('redirects /@:username to the dashboard', async () => {
const { res, captured } = makeRes();
await callRoute(
@@ -263,14 +263,29 @@ export class HomepageController extends PuterController {
});
}
router.get('/settings', {}, (req, res) => sendShell(req, res));
router.get('/settings/*splat', {}, (req, res) => sendShell(req, res));
// Old settings links land on the dashboard's Home tab.
router.get('/settings', {}, (_req, res) =>
res.redirect('/dashboard#home'),
);
router.get('/settings/*splat', {}, (_req, res) =>
res.redirect('/dashboard#home'),
);
router.get('/dashboard', {}, (req, res) => sendShell(req, res));
router.get('/dashboard/', {}, (req, res) => sendShell(req, res));
router.get('/desktop', {}, (req, res) => sendShell(req, res));
router.get('/desktop/', {}, (req, res) => sendShell(req, res));
const queryOf = (req: express.Request) => {
const separator = req.originalUrl.indexOf('?');
return separator === -1 ? '' : req.originalUrl.slice(separator);
};
// Old desktop links land on the dashboard, query intact.
router.get('/desktop', {}, (req, res) =>
res.redirect(`/${queryOf(req)}`),
);
router.get('/desktop/', {}, (req, res) =>
res.redirect(`/${queryOf(req)}`),
);
router.get('/action/*splat', {}, (req, res) => sendShell(req, res));
@@ -279,11 +294,8 @@ export class HomepageController extends PuterController {
// Old desktop app links open the app in the dashboard, query intact.
router.get('/desktop/app/:name', {}, (req, res) => {
const separator = req.originalUrl.indexOf('?');
const query =
separator === -1 ? '' : req.originalUrl.slice(separator);
const name = encodeURIComponent(String(req.params.name ?? ''));
res.redirect(`/app/${name}${query}`);
res.redirect(`/app/${name}${queryOf(req)}`);
});
// -- /app/:name - app metadata baked into the shell ----------
@@ -380,6 +380,7 @@ describe('OIDCController GET /auth/oidc/:provider/start', () => {
'//evil.test',
'/settings',
`/app/${'a'.repeat(101)}`,
'/desktop',
'/desktop/app/my-App_2',
'/desktop/app/evil/extra',
'/desktop/app/',
@@ -457,11 +458,11 @@ describe('OIDCController GET /auth/oidc/:provider/start', () => {
sharedPath('b.txt'),
sharedPath('a.txt'),
],
'/desktop',
'/dashboard',
),
);
const url = new URL(uri);
expect(url.origin + url.pathname).toBe(`${TEST_ORIGIN}/desktop`);
expect(url.origin + url.pathname).toBe(`${TEST_ORIGIN}/dashboard`);
expect(url.searchParams.getAll('shared')).toEqual([
sharedPath('a.txt'),
sharedPath('b.txt'),
@@ -102,13 +102,12 @@ function signupOptionsFromState(
}
// GUI pages an OIDC flow may return to: the root (where a share email lands),
// /desktop, /dashboard, and direct app landings (/app/<name>, mirroring
// /dashboard, and direct app landings (/app/<name>, mirroring
// APP_NAME_REGEX in AppDriver). Strict whitelist — never a client-supplied URL
// (no open redirect).
function isWhitelistedReturnPath(path: string): boolean {
return (
path === '/' ||
path === '/desktop' ||
path === '/dashboard' ||
/^\/app\/[a-zA-Z0-9_-]{1,100}$/.test(path)
);
@@ -437,9 +436,8 @@ export class OIDCController extends PuterController {
let appRedirectUri = flowRedirects[flow] ?? (origin || '/');
// Optional GUI return path so login started from /desktop,
// /dashboard, an /app/<name> landing, or a share link lands
// back there.
// Optional GUI return path so login started from /dashboard,
// an /app/<name> landing, or a share link lands back there.
const rawReturnTo = Array.isArray(req.query.return_to)
? req.query.return_to[0]
: req.query.return_to;
@@ -314,8 +314,8 @@ describe('OIDC account second-factor login', () => {
it('preserves ordinary OIDC login for accounts without 2FA', async () => {
await account(false);
const response = await callback('login', { return_to: '/desktop' });
expect(response.headers.get('location')).toBe(`${env.origin}/desktop`);
const response = await callback('login', { return_to: '/dashboard' });
expect(response.headers.get('location')).toBe(`${env.origin}/dashboard`);
expect(
response.headers.get('set-cookie')?.includes('puter_auth_token='),
).toBe(true);
+1 -1
View File
@@ -144,7 +144,7 @@ try {
startServer(1);
app.get(['/', '/app/*splat', '/action/*splat', '/desktop', '/dashboard'], (req, res) => {
app.get(['/', '/app/*splat', '/action/*splat', '/dashboard'], (req, res) => {
res.send(generateDevHtml({
env: env,
api_origin: apiOrigin,
+2 -3
View File
@@ -424,15 +424,14 @@ export const authHeadHtml = (flow, title, { openerNotice = true } = {}) => {
};
/**
* On a cover page, the logo is a way out: desktop users stay on the desktop,
* everyone else goes to the root dashboard.
* On a cover page, the logo is a way out, to the root dashboard.
*
* @param {AuthFlow} flow
* @returns {string} Empty where the logo leads nowhere.
*/
const logoHref = (flow) => {
if ( ! flow.page || window.embedded_in_popup ) return '';
return window.location.pathname === '/desktop' ? '/desktop' : '/';
return '/';
};
/**
+10 -398
View File
@@ -19,14 +19,10 @@
import path from '../lib/path.js';
import UIContextMenu from './UIContextMenu.js';
import UIItem from './UIItem.js';
import UIAlert from './UIAlert.js';
import UIWindow from './UIWindow.js';
import UIWindowSaveAccount from './UIWindowSaveAccount.js';
import new_context_menu_item from '../helpers/newContextMenuItem.js';
import refresh_item_container from '../helpers/refreshItemContainer.js';
import { atPath, retargetNavHistory, retargetPaths, underPath } from '../helpers/pathElements.js';
import truncate_filename from '../helpers/truncateFilename.js';
import UINotification from './UINotification.js';
@@ -59,7 +55,7 @@ import apply_item_added_to_containers from '../helpers/applyItemAddedToContainer
import parse_item_metadata from '../helpers/parseItemMetadata.js';
import { resolve_default_app } from '../helpers/defaultApp.js';
async function UIDesktop (options) {
async function UIDesktop () {
// start a transaction if we're not in embedded or fullpage mode
let transaction;
if ( !window.is_embedded && !window.is_fullpage_mode ) {
@@ -658,16 +654,8 @@ async function UIDesktop (options) {
h += '<div class="window-container"></div>';
// Desktop
// If desktop is not in fullpage/embedded mode, we hide it until files and directories are loaded and then fade in the UI
// This gives a calm and smooth experience for the user
h += `<div class="desktop item-container disable-user-select"
data-uid="${options.desktop_fsentry?.uid ?? ''}"
data-sort_by="${!options.desktop_fsentry?.sort_by ? 'name' : options.desktop_fsentry.sort_by}"
data-sort_order="${!options.desktop_fsentry?.sort_order ? 'asc' : options.desktop_fsentry.sort_order}"
data-path="${html_encode(window.desktop_path)}"
>`;
h += '</div>';
// The surface windows open on. It holds no items.
h += '<div class="desktop disable-user-select"></div>';
// Get window sidebar width
puter.kv.get('window_sidebar_width').catch(() => null).then(async (val) => {
@@ -706,387 +694,19 @@ async function UIDesktop (options) {
const el_desktop = document.querySelector('.desktop');
window.active_element = el_desktop;
window.active_item_container = el_desktop;
// --------------------------------------------------------
// Dragster
// Allow dragging of local files onto desktop.
// --------------------------------------------------------
$(el_desktop).dragster({
enter: function (dragsterEvent, event) {
$('.context-menu').remove();
},
leave: function (dragsterEvent, event) {
},
drop: async function (dragsterEvent, event) {
const e = event.originalEvent;
// no drop on item
if ( $(event.target).hasClass('item') || $(event.target).parent('.item').length > 0 )
{
return false;
}
// recursively create directories and upload files
if ( e.dataTransfer?.items?.length > 0 ) {
window.upload_items(e.dataTransfer.items, window.desktop_path);
}
e.stopPropagation();
e.preventDefault();
return false;
},
// The desktop has no menu of its own, and the browser's doesn't belong here.
$(el_desktop).on('contextmenu', function (event) {
if ( event.target === el_desktop ) event.preventDefault();
});
// --------------------------------------------------------
// Droppable
// --------------------------------------------------------
$(el_desktop).droppable({
accept: '.item',
tolerance: 'intersect',
drop: function (event, ui) {
// Check if item was actually dropped on desktop and not a window
if ( window.mouseover_window !== undefined )
{
return;
}
// Can't drop anything but UIItems on desktop
if ( ! $(ui.draggable).hasClass('item') )
{
return;
}
// Don't move an item to its current directory
if ( path.dirname($(ui.draggable).attr('data-path')) === window.desktop_path && !event.ctrlKey )
{
return;
}
// If ctrl is pressed and source is Trashed, cancel whole operation
if ( event.ctrlKey && path.dirname($(ui.draggable).attr('data-path')) === window.trash_path )
{
return;
}
// Unselect previously selected items
$(el_desktop).children('.item-selected').removeClass('item-selected');
const items_to_move = [];
// first item
items_to_move.push(ui.draggable);
// all subsequent items
const cloned_items = document.getElementsByClassName('item-selected-clone');
for ( let i = 0; i < cloned_items.length; i++ ) {
const source_item = document.getElementById(`item-${ $(cloned_items[i]).attr('data-id')}`);
if ( source_item !== null )
{
items_to_move.push(source_item);
}
}
// if ctrl key is down, copy items
if ( event.ctrlKey ) {
// unless source is Trash
if ( path.dirname($(ui.draggable).attr('data-path')) === window.trash_path )
{
return;
}
window.copy_items(items_to_move, window.desktop_path);
}
// otherwise, move items
else {
window.move_items(items_to_move, window.desktop_path);
}
},
});
//--------------------------------------------------
// ContextMenu
//--------------------------------------------------
$(el_desktop).bind('contextmenu taphold', function (event) {
// dismiss taphold on regular devices
if ( event.type === 'taphold' && !isMobile.phone && !isMobile.tablet )
{
return;
}
const $target = $(event.target);
// elements that should retain native ctxmenu
if ( $target.is('input') || $target.is('textarea') )
{
return true;
}
// custom ctxmenu for all other elements
if ( event.target === el_desktop ) {
event.preventDefault();
UIContextMenu({
position: event.type === 'taphold' ? undefined : { left: event.pageX, top: event.pageY },
items: [
// -------------------------------------------
// Sort by
// -------------------------------------------
{
html: i18n('sort_by'),
items: [
{
html: i18n('auto_arrange'),
icon: window.is_auto_arrange_enabled ? '✓' : '',
onClick: async function () {
window.is_auto_arrange_enabled = !window.is_auto_arrange_enabled;
window.store_auto_arrange_preference(window.is_auto_arrange_enabled);
if ( window.is_auto_arrange_enabled ) {
window.sort_items(el_desktop, $(el_desktop).attr('data-sort_by'), $(el_desktop).attr('data-sort_order'));
window.set_sort_by(options.desktop_fsentry.uid, $(el_desktop).attr('data-sort_by'), $(el_desktop).attr('data-sort_order'));
window.clear_desktop_item_positions(el_desktop);
} else {
window.set_desktop_item_positions(el_desktop);
}
},
},
// -------------------------------------------
// -
// -------------------------------------------
'-',
{
html: i18n('name'),
disabled: !window.is_auto_arrange_enabled,
icon: $(el_desktop).attr('data-sort_by') === 'name' ? '✓' : '',
onClick: async function () {
window.sort_items(el_desktop, 'name', $(el_desktop).attr('data-sort_order'));
window.set_sort_by(options.desktop_fsentry.uid, 'name', $(el_desktop).attr('data-sort_order'));
},
},
{
html: i18n('date_modified'),
disabled: !window.is_auto_arrange_enabled,
icon: $(el_desktop).attr('data-sort_by') === 'modified' ? '✓' : '',
onClick: async function () {
window.sort_items(el_desktop, 'modified', $(el_desktop).attr('data-sort_order'));
window.set_sort_by(options.desktop_fsentry.uid, 'modified', $(el_desktop).attr('data-sort_order'));
},
},
{
html: i18n('type'),
disabled: !window.is_auto_arrange_enabled,
icon: $(el_desktop).attr('data-sort_by') === 'type' ? '✓' : '',
onClick: async function () {
window.sort_items(el_desktop, 'type', $(el_desktop).attr('data-sort_order'));
window.set_sort_by(options.desktop_fsentry.uid, 'type', $(el_desktop).attr('data-sort_order'));
},
},
{
html: i18n('size'),
disabled: !window.is_auto_arrange_enabled,
icon: $(el_desktop).attr('data-sort_by') === 'size' ? '✓' : '',
onClick: async function () {
window.sort_items(el_desktop, 'size', $(el_desktop).attr('data-sort_order'));
window.set_sort_by(options.desktop_fsentry.uid, 'size', $(el_desktop).attr('data-sort_order'));
},
},
// -------------------------------------------
// -
// -------------------------------------------
'-',
{
html: i18n('ascending'),
disabled: !window.is_auto_arrange_enabled,
icon: $(el_desktop).attr('data-sort_order') === 'asc' ? '✓' : '',
onClick: async function () {
const sort_by = $(el_desktop).attr('data-sort_by');
window.sort_items(el_desktop, sort_by, 'asc');
window.set_sort_by(options.desktop_fsentry.uid, sort_by, 'asc');
},
},
{
html: i18n('descending'),
disabled: !window.is_auto_arrange_enabled,
icon: $(el_desktop).attr('data-sort_order') === 'desc' ? '✓' : '',
onClick: async function () {
const sort_by = $(el_desktop).attr('data-sort_by');
window.sort_items(el_desktop, sort_by, 'desc');
window.set_sort_by(options.desktop_fsentry.uid, sort_by, 'desc');
},
},
],
},
// -------------------------------------------
// Refresh
// -------------------------------------------
{
html: i18n('refresh'),
onClick: function () {
refresh_item_container(el_desktop, { consistency: 'strong' });
},
},
// -------------------------------------------
// Show/Hide hidden files
// -------------------------------------------
{
html: i18n('show_hidden'),
icon: window.user_preferences.show_hidden_files ? '✓' : '',
onClick: function () {
window.mutate_user_preferences({
show_hidden_files: !window.user_preferences.show_hidden_files,
});
window.show_or_hide_files(document.querySelectorAll('.item-container'));
},
},
// -------------------------------------------
// -
// -------------------------------------------
'-',
// -------------------------------------------
// New File
// -------------------------------------------
new_context_menu_item(window.desktop_path, el_desktop),
// -------------------------------------------
// -
// -------------------------------------------
'-',
// -------------------------------------------
// Paste
// -------------------------------------------
{
html: i18n('paste'),
disabled: window.clipboard.length > 0 ? false : true,
onClick: function () {
if ( window.clipboard_op === 'copy' )
{
window.copy_clipboard_items(window.desktop_path, el_desktop);
}
else if ( window.clipboard_op === 'move' )
{
window.move_clipboard_items(el_desktop);
}
},
},
// -------------------------------------------
// Undo
// -------------------------------------------
{
html: i18n('undo'),
disabled: window.actions_history.length > 0 ? false : true,
onClick: function () {
window.undo_last_action();
},
},
// -------------------------------------------
// Upload Here
// -------------------------------------------
{
html: i18n('upload_here'),
onClick: function () {
window.init_upload_using_dialog(el_desktop);
},
},
],
});
}
});
//-------------------------------------------
// Desktop Files/Folders
// we don't need to get the desktop items if we're in embedded or fullpage mode
// because the items aren't visible anyway and we don't need to waste bandwidth/server resources
//-------------------------------------------
if ( !window.is_embedded && !window.is_fullpage_mode ) {
refresh_item_container(el_desktop, {
fadeInItems: true,
onComplete: () => {
// End transaction when desktop is fully ready for user interaction
transaction.end();
},
});
transaction.end();
// perform readdirs for caching purposes
// home directory
// warm the home directory listing for the first Explorer window
puter.fs.readdir({ path: window.home_path, consistency: 'strong' });
}
// -------------------------------------------
// Selectable
// Only for desktop
// -------------------------------------------
if ( !isMobile.phone && !isMobile.tablet ) {
let selected_ctrl_items = [];
const selection = new SelectionArea({
selectionContainerClass: '.selection-area-container',
container: '.desktop',
selectables: ['.desktop.item-container > .item'],
startareas: ['.desktop'],
boundaries: ['.desktop'],
behaviour: {
overlap: 'drop',
intersect: 'touch',
startThreshold: 10,
scrolling: {
speedDivider: 10,
manualSpeed: 750,
startScrollMargins: { x: 0, y: 0 },
},
},
features: {
touch: true,
range: true,
singleTap: {
allow: true,
intersect: 'native',
},
},
});
selection.on('beforestart', ({ event }) => {
selected_ctrl_items = [];
// Returning false prevents a selection
return $(event.target).hasClass('item-container');
})
.on('beforedrag', evt => {
})
.on('start', ({ store, event }) => {
if ( !event.ctrlKey && !event.metaKey ) {
for ( const el of store.stored ) {
el.classList.remove('item-selected');
}
selection.clearSelection();
}
// mark desktop as selectable active
$('.desktop').addClass('desktop-selectable-active');
})
.on('move', ({ store: { changed: { added, removed } }, event }) => {
window.desktop_selectable_is_active = true;
for ( const el of added ) {
// if ctrl or meta key is pressed and the item is already selected, then unselect it
if ( (event.ctrlKey || event.metaKey) && $(el).hasClass('item-selected') ) {
el.classList.remove('item-selected');
selected_ctrl_items.push(el);
}
// otherwise select it
else {
el.classList.add('item-selected');
}
}
for ( const el of removed ) {
el.classList.remove('item-selected');
// in case this item was selected by ctrl+click before, then reselect it again
if ( selected_ctrl_items.includes(el) )
{
$(el).not('.item-disabled').addClass('item-selected');
}
}
})
.on('stop', evt => {
window.desktop_selectable_is_active = false;
$('.desktop').removeClass('desktop-selectable-active');
});
}
// notification container
$('body').append(`<div class="notification-container"><div class="notifications-close-all">${i18n('close_all')}</div></div>`);
@@ -1126,12 +746,6 @@ async function UIDesktop (options) {
}
}
}
//--------------------------------------------------------------------------------------
// /settings redirects to /dashboard
//--------------------------------------------------------------------------------------
else if ( window.url_paths[0]?.toLocaleLowerCase() === 'settings' ) {
window.open('/dashboard/#home', '_blank');
}
// ---------------------------------------------
// load from direct app URLs: /app/app-name
// ---------------------------------------------
@@ -1171,10 +785,10 @@ async function UIDesktop (options) {
return;
}
// disable pointer-events for all app iframes, this is to make sure selectable works
$('.window-app-iframe').css('pointer-events', 'none');
$('.window').find('.item-selected').addClass('item-blurred');
$('.desktop').find('.item-blurred').removeClass('item-blurred');
// Keyboard shortcuts act on the active container, and a click here leaves every window's.
window.active_item_container = null;
});
$(el_desktop).on('click', function (e) {
@@ -1238,7 +852,6 @@ async function UIDesktop (options) {
file_path: item_path,
app_obj: suggested_apps[0],
window_title: path.basename(item_path),
maximized: options.maximized,
file_signature: open_item_meta.signature,
custom_path: window.location.pathname,
});
@@ -1292,7 +905,6 @@ window.enter_fullpage_mode = (el_window) => {
window.exit_fullpage_mode = (el_window) => {
$('body').removeClass('fullpage-mode');
refresh_item_container($('.desktop.item-container'), { fadeInItems: true });
$(el_window).removeAttr('data-is_fullpage');
if ( el_window ) {
window.reset_window_size_and_position(el_window);
+1 -32
View File
@@ -234,17 +234,6 @@ async function UIItem (options) {
window.update_explorer_footer_item_count(el_window);
}
// manual positioning
if ( !window.is_auto_arrange_enabled &&
options.position &&
// item is on the desktop (must be desktop itself and not a window, hence the '.desktop' class check)
$(el_item).closest('.item-container.desktop').attr('data-path') === window.desktop_path
) {
el_item.style.position = 'absolute';
el_item.style.left = `${options.position.left }px`;
el_item.style.top = `${options.position.top }px`;
}
// --------------------------------------------------------
// Dragster
// allow dragging of local files on this window, if it's is_dir
@@ -409,21 +398,6 @@ async function UIItem (options) {
}
},
stop: function (event, ui) {
// Allow rearranging only if item is on desktop, not trash container, auto arrange is disabled and item is not dropped into another item
// the desktop itself, not a window showing the Desktop folder
if ( $(el_item).closest('.item-container').hasClass('desktop') &&
!window.is_auto_arrange_enabled && $(el_item).attr('data-path') !== window.trash_path && !ui.helper.data('dropped') &&
// .desktop clips its overflow, so the whole item has to fit above the bottom edge
window.mouseover_window === undefined && ui.position.top + $(el_item).outerHeight(true) <= window.desktop_height ) {
el_item.style.position = 'absolute';
el_item.style.left = `${ui.position.left }px`;
el_item.style.top = `${ui.position.top }px`;
$('.ui-draggable-dragging').remove();
window.desktop_item_positions[$(el_item).attr('data-uid')] = ui.position;
window.save_desktop_item_positions();
}
$('.item-selected-clone').remove();
$('.draggable-count-badge').remove();
// re-enable all droppable UIItems that are not a dir
@@ -546,11 +520,6 @@ async function UIItem (options) {
else if ( options.is_dir ) {
// a selected folder can be the drop target of its own selection; it stays where it is
const items_to_drop = items_to_move.filter(item => $(item).attr('data-uid') !== $(el_item).attr('data-uid'));
if ( $(el_item).closest('.item-container').attr('data-path') === window.desktop_path ) {
// the items leaving the desktop lose their saved positions, not the folder they land in
items_to_drop.forEach(item => delete window.desktop_item_positions[$(item).attr('data-uid')]);
window.save_desktop_item_positions();
}
window.move_items(items_to_drop, $(el_item).attr('data-shortcut_to_path') !== '' ? $(el_item).attr('data-shortcut_to_path') : $(el_item).attr('data-path'));
}
}
@@ -1553,7 +1522,7 @@ async function UIItem (options) {
// Create ContextMenu
UIContextMenu({
parent_element: ($(options.appendTo).hasClass('desktop') ? undefined : options.appendTo),
parent_element: options.appendTo,
position: menu_position,
items: menu_items,
});
-50
View File
@@ -195,53 +195,3 @@ describe('UIItem Publish as Website', () => {
expect(labels).toContain('deploy_as_app');
});
});
describe('UIItem dropped on the desktop', () => {
let stop;
beforeEach(async () => {
document.body.innerHTML = '<div class="item-container desktop"></div>';
Object.assign(window, {
user: { username: 'alice' },
trash_path: '/alice/Trash',
desktop_height: 800,
is_auto_arrange_enabled: false,
mouseover_window: undefined,
desktop_item_positions: {},
save_desktop_item_positions: vi.fn(),
});
const draggable = jQuery.fn.draggable;
jQuery.fn.draggable = function (options) {
if ( typeof options === 'object' ) stop = options.stop;
return this;
};
try {
await UIItem({
appendTo: $('.item-container'),
uid: 'u-2',
path: '/alice/Desktop/Notes',
name: 'Notes',
is_dir: true,
icon: { image: '', type: 'icon' },
});
} finally {
jQuery.fn.draggable = draggable;
}
// jsdom has no stylesheet, so give the item the size style.css does.
$('.item').css({ height: '70px', margin: '15px 5px 30px 5px' });
});
const dropAt = (top) => stop({}, { position: { top, left: 300 }, helper: $('<div>') });
it('moves there when the whole item fits on screen', () => {
dropAt(685);
expect($('.item')[0].style.top).toBe('685px');
expect(window.save_desktop_item_positions).toHaveBeenCalledTimes(1);
});
it('stays put when the item would hang off the bottom edge', () => {
dropAt(735);
expect($('.item')[0].style.top).toBe('');
expect(window.save_desktop_item_positions).not.toHaveBeenCalled();
});
});
+3 -12
View File
@@ -3692,9 +3692,7 @@ $.fn.close = async function (options) {
document.activeElement.blur();
$('input').blur();
}
// focus back to desktop
$('.desktop').find('.item-blurred').removeClass('item-blurred');
window.active_item_container = $('.desktop.item-container').get(0);
window.active_item_container = null;
}
});
@@ -3975,12 +3973,6 @@ $.fn.showWindow = async function (options) {
};
window.toggle_empty_folder_message = function (el_item_container) {
// if the item container is the desktop, don't show/hide the empty message
if ( $(el_item_container).hasClass('desktop') )
{
return;
}
// if the item container is empty, show the empty message
if ( $(el_item_container).has('.item').length === 0 ) {
$(el_item_container).find('.explorer-empty-message').show();
@@ -4081,8 +4073,8 @@ $.fn.focusWindow = function (event) {
// remove blurred class from items on this window
$(window.active_item_container).find('.item-blurred').removeClass('item-blurred');
// Update the tab title to the focused window. The URL itself is
// deliberately NOT touched here: on the desktop it stays /desktop
// (apps don't rewrite it), and in dashboard mode the open app owns
// deliberately NOT touched here: on the desktop apps don't rewrite
// it, and in dashboard mode the open app owns
// it through real history entries (push_dashboard_app_url).
if ( ! window.is_dashboard_mode ) {
const update_window_url = $(this).attr('data-update_window_url');
@@ -4092,7 +4084,6 @@ $.fn.focusWindow = function (event) {
}
} else {
$('.window').find('.item-selected').addClass('item-blurred');
$('.desktop').find('.item-blurred').removeClass('item-blurred');
}
return this;
-53
View File
@@ -361,28 +361,15 @@ input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, sel
}
.desktop {
display: none;
overflow: hidden;
height: 100vh !important;
width: 100%;
box-sizing: border-box;
display: grid;
grid-template-rows: repeat(auto-fill, 109px);
grid-auto-flow: column;
grid-template-columns: repeat(auto-fill, 120px);
padding-top: 15px;
}
.device-desktop .desktop {
padding-top: 5px;
}
.device-phone .desktop {
height: 100vh !important;
height: 100dvh !important;
padding-left: 0 !important;
padding-right: 0 !important;
padding-bottom: 0 !important;
}
.item-container-list {
@@ -884,18 +871,6 @@ span.header-sort-icon img {
margin-left: 3px;
}
/* The desktop is a plain page color, so its labels need text that reads on it. */
.desktop .item .item-name {
color: rgb(73, 73, 73);
text-shadow: none;
}
@media (prefers-color-scheme: dark) {
.desktop .item .item-name {
color: #e6e6e6;
}
}
.device-phone .item-container-list .item .item-name {
line-height: 42px;
border-bottom: 1px solid #e3e3e3;
@@ -5138,34 +5113,6 @@ html.dark-mode .usage-table-fade-overlay {
border-radius: 0;
}
.device-phone .desktop {
position: relative;
/* Enable smooth scrolling on iOS */
-webkit-overflow-scrolling: touch;
/* Allow vertical touch scrolling while preventing horizontal */
touch-action: pan-x;
/* Hide scrollbars while keeping functionality */
scrollbar-width: none;
-ms-overflow-style: none;
/* Scrolling behavior */
overflow-y: visible !important;
overflow-x: scroll;
padding-bottom: 60px;
}
.device-phone .desktop * {
touch-action: pan-x;
}
.device-phone .desktop::-webkit-scrollbar {
display: none;
}
/* height of 100% should not be applied to file dialogs */
.device-phone .window:not(.window-filedialog) .window-body.item-container {
height: 100%;
-10
View File
@@ -25,12 +25,9 @@ window.window_nav_history_current_position = {};
window.dashboard_nav_history = [];
window.dashboard_nav_history_current_position = 0;
window.progress_tracker = [];
window.upload_item_global_id = 0;
window.app_instance_ids = new Set();
window.menubars = [];
window.download_progress = [];
window.download_item_global_id = 0;
// This is the minimum width of the window for the sidebar to be shown
window.window_width_threshold_for_sidebar = 500;
@@ -128,7 +125,6 @@ window.window_counter = 0;
window.keypress_item_seach_term = '';
window.keypress_item_seach_buffer_timeout = undefined;
window.first_visit_animation = false;
window.show_twitter_link = true;
window.animate_window_opening = true;
window.animate_window_closing = true;
window.desktop_loading_fade_delay = (window.first_visit_ever && window.first_visit_animation ? 6000 : 1000);
@@ -151,9 +147,6 @@ window.launch_apps = [];
window.launch_apps.recent = [];
window.launch_apps.recommended = [];
// Map of { child_instance_id -> { parent_instance_id, launch_msg_id } }
window.child_launch_callbacks = {};
// Is puter being loaded inside an iframe?
if ( window.location !== window.parent.location ) {
window.is_embedded = true;
@@ -216,9 +209,6 @@ window.feature_flags = {
download_directory: true,
};
window.is_auto_arrange_enabled = true;
window.desktop_item_positions = {};
window.reset_item_positions = true; // The variable decides if the item positions should be reset when the user enabled auto arrange
window.file_templates = [];
-100
View File
@@ -3416,57 +3416,6 @@ window.undo_delete = async (items) => {
}
};
window.store_auto_arrange_preference = (preference) => {
// localStorage still carries it for this device either way.
puter.kv.set('user_preferences.auto_arrange_desktop', preference)
.catch(err => console.warn('Could not save auto_arrange_desktop:', err));
localStorage.setItem('auto_arrange', preference);
};
window.get_auto_arrange_data = async () => {
// Best-effort, like every other preference read: falling back to the
// default keeps the desktop arranging itself rather than not appearing.
const preferenceValue = await puter.kv.get('user_preferences.auto_arrange_desktop').catch(() => null);
window.is_auto_arrange_enabled = preferenceValue === null ? true : preferenceValue;
const positions = await puter.kv.get('desktop_item_positions').catch(() => null);
window.desktop_item_positions = (!positions || typeof positions !== 'object' || Array.isArray(positions)) ? {} : positions;
};
window.clear_desktop_item_positions = async (el_desktop) => {
$(el_desktop).find('.item').each(function () {
const el_item = $(this)[0];
$(el_item).css('position', '');
$(el_item).css('left', '');
$(el_item).css('top', '');
});
if ( window.reset_item_positions ) {
window.delete_desktop_item_positions();
}
};
window.set_desktop_item_positions = async (el_desktop) => {
$(el_desktop).find('.item').each(async function () {
const position = window.desktop_item_positions[$(this).attr('data-uid')];
const el_item = $(this)[0];
if ( position ) {
$(el_item).css('position', 'absolute');
$(el_item).css('left', `${position.left }px`);
$(el_item).css('top', `${position.top }px`);
}
});
};
window.save_desktop_item_positions = () => {
puter.kv.set('desktop_item_positions', window.desktop_item_positions)
.catch(err => console.warn('Could not save desktop_item_positions:', err));
};
window.delete_desktop_item_positions = () => {
window.desktop_item_positions = {};
puter.kv.del('desktop_item_positions')
.catch(err => console.warn('Could not clear desktop_item_positions:', err));
};
// Finds the `.window` element for the given app instance ID
window.window_for_app_instance = (instance_id) => {
return $(`.window[data-element_uuid="${instance_id}"]`).get(0);
@@ -3522,22 +3471,6 @@ window.set_menu_item_prop = (items, item_id, prop, val) => {
}
};
window.countSubstr = (str, substring) => {
if ( substring.length === 0 ) {
return 0;
}
let count = 0;
let pos = str.indexOf(substring);
while ( pos !== -1 ) {
count++;
pos = str.indexOf(substring, pos + 1);
}
return count;
};
// Updates the signed-in user's profile; `username` is kept for callers that
// still pass it.
window.update_profile = function (username, key_vals) {
@@ -3552,44 +3485,11 @@ window.update_profile = function (username, key_vals) {
});
};
window.blob2str = (blob) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsText(blob);
});
};
// Another user's picture is only available while they are on a paid plan.
window.get_profile_picture = async function (username) {
return (await puter.auth.getProfilePicture(username)) ?? undefined;
};
window.format_with_units = (num, { mulUnits, divUnits, precision = 3 }) => {
if ( num === 0 ) return '0';
mulUnits = mulUnits ?? ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
divUnits = divUnits ?? ['m', 'µ', 'n', 'p', 'f', 'a', 'z', 'y'];
const abs = Math.abs(num);
let exp = Math.floor(Math.log10(abs) / 3);
let symbol = '';
symbol = exp >= 0
? mulUnits[exp]
: divUnits[-exp - 1] ;
if ( ! symbol ) {
symbol = `e${exp * 3}`;
}
const scaled = num / Math.pow(10, exp * 3);
const rounded = Number.parseFloat(scaled.toPrecision(precision));
return `${rounded}${symbol}`;
};
/**
* General-purpose number formatting function with support for decimal places,
* thousand separators, and various formatting options.
+2 -2
View File
@@ -57,8 +57,8 @@ describe('authLogoHeader', () => {
});
it('makes the logo a named link when it leads somewhere', () => {
const h = authLogoHeader({ logoSrc: LOGO, logoHref: '/desktop' });
expect(h).toContain('<a class="auth-logo-link" href="/desktop">');
const h = authLogoHeader({ logoSrc: LOGO, logoHref: '/' });
expect(h).toContain('<a class="auth-logo-link" href="/">');
expect(h).toContain('alt="Puter"');
});
+6 -7
View File
@@ -63,7 +63,7 @@ export const get_auth_redirect_url = () => {
*/
const oidc_return_path = () => {
const pathname = window.location.pathname;
if ( pathname === '/' || pathname === '/desktop' || pathname === '/dashboard' ) {
if ( pathname === '/' || pathname === '/dashboard' ) {
return pathname;
}
// app landing: normalize away a trailing slash to match the backend whitelist
@@ -76,12 +76,11 @@ const oidc_return_path = () => {
/**
* The `return_to` to send along when starting an OIDC flow, or null if the
* current page isn't one the backend will return to. The backend strictly
* whitelists these (never a client-supplied URL): `/desktop`, `/dashboard`,
* and direct app landings (`/app/<name>`), so OIDC login started from an app
* landing comes back to the app.
* whitelists these (never a client-supplied URL): `/dashboard` and direct app
* landings (`/app/<name>`), so OIDC login started from an app landing comes
* back to the app.
*
* A share link (`?shared=`, from an email) is carried along with the path, except
* from `/desktop`, which doesn't open share links: the
* A share link (`?shared=`, from an email) is carried along with the path: the
* recipient usually has to sign in before they can see what was shared, and an
* OIDC round trip leaves the origin, so the parameter has to travel through the
* flow or they come back to a bare Home. Only well-formed values go — the
@@ -93,7 +92,7 @@ export const get_oidc_return_to = () => {
const path = oidc_return_path();
if ( path === null ) return null;
const shared = path === '/desktop' ? [] : new URLSearchParams(window.location.search ?? '')
const shared = new URLSearchParams(window.location.search ?? '')
.getAll(SHARED_PATH_PARAM)
.filter(value => parse_shared_path(value) !== null);
if ( shared.length === 0 ) {
+1 -5
View File
@@ -42,7 +42,6 @@ afterEach(() => {
describe('get_oidc_return_to', () => {
it('returns the interface pages the backend whitelists', () => {
expect(at('/desktop')).toBe('/desktop');
expect(at('/dashboard')).toBe('/dashboard');
});
@@ -84,15 +83,12 @@ describe('get_oidc_return_to', () => {
).toBe(`/dashboard${share_search(shared_path('a.txt'))}`);
});
it('drops a share link on the desktop, which does not open one', () => {
expect(at('/desktop', share_search(shared_path('Report.pdf')))).toBe('/desktop');
});
it('returns null for anything the backend would reject', () => {
expect(at('/')).toBe(null);
expect(at('/settings')).toBe(null);
expect(at('/action/login')).toBe(null);
expect(at('/app/editor/extra')).toBe(null);
expect(at('/desktop')).toBe(null);
expect(at('/desktop/app/editor')).toBe(null);
expect(at('/desktop/app/editor/extra')).toBe(null);
expect(at('/dashboard/app/editor')).toBe(null);
+2 -3
View File
@@ -33,9 +33,8 @@ describe('bootsDashboard', () => {
expect(boots('/app/editor')).toBe(true);
});
it('boots the desktop at /desktop', () => {
expect(boots('/desktop')).toBe(false);
expect(boots('/desktop/app/editor')).toBe(false);
it('boots the desktop on action pages', () => {
expect(boots('/action/sign-in')).toBe(false);
});
it('boots the desktop for flows only the desktop handles', () => {
@@ -246,7 +246,6 @@ const refresh_item_container = function (el_item_container, options) {
if ( item_path !== window.trash_path && item_path !== window.appdata_path ) {
// if this is trash, get original name from item metadata
fsentry.name = metadata.original_name ?? fsentry.name;
const position = window.desktop_item_positions[fsentry.uid] ?? undefined;
UIItem({
appendTo: el_item_container,
uid: fsentry.uid,
@@ -269,7 +268,6 @@ const refresh_item_container = function (el_item_container, options) {
suggested_apps: fsentry.suggested_apps,
disabled: is_disabled,
visible: visible,
position: position,
shared_with_me: fsentry.shared_with_me,
share_mode: fsentry.share_mode,
shared_by: fsentry.shared_by,
@@ -281,31 +279,6 @@ const refresh_item_container = function (el_item_container, options) {
}
}
// if this is desktop, add Trash
if ( $(el_item_container).hasClass('desktop') ) {
try {
const trash = await puter.fs.stat({ path: window.trash_path, consistency: options.consistency ?? 'eventual' });
if ( ! is_current() ) {
return;
}
UIItem({
appendTo: el_item_container,
uid: trash.id,
immutable: trash.immutable,
path: window.trash_path,
icon: { image: (trash.is_empty ? window.icons['trash.svg'] : window.icons['trash-full.svg']), type: 'icon' },
name: trash.name,
is_dir: trash.is_dir,
sort_by: trash.sort_by,
type: trash.type,
is_trash: true,
sortable: false,
});
window.sort_items(el_item_container, $(el_item_container).attr('data-sort_by'), $(el_item_container).attr('data-sort_order'));
} catch (e) {
// Ignored
}
}
// sort items
window.sort_items(
el_item_container,
@@ -47,7 +47,6 @@ Object.assign(window, {
shared_path: '/alice/Shared',
trash_path: '/alice/Trash',
user_preferences: {},
desktop_item_positions: {},
check_fsentry_against_allowed_file_types_string: () => true,
sort_items: () => {},
update_explorer_footer_item_count: () => {},
-1
View File
@@ -31,7 +31,6 @@ const ar = {
allow: 'السماح',
ascending: 'تصاعدي',
associated_websites: 'المواقع المرتبطة',
auto_arrange: 'ترتيب تلقائي',
cancel: 'إلغاء',
change_email: 'تغيير البريد الإلكتروني',
change_language: 'تغيير اللغة',
-1
View File
@@ -31,7 +31,6 @@ const bg = {
allow: 'Разреши',
ascending: 'Възходящ',
associated_websites: 'Свързани уебсайтове',
auto_arrange: 'Автоматично подреждане',
cancel: 'Отказ',
change: 'Промени',
change_always_open_with: 'Искате ли винаги да отваряте този тип файл с',
-1
View File
@@ -31,7 +31,6 @@ const bn = {
allow: 'অনুমতি দিন',
ascending: 'ঊর্ধ্বক্রমে',
associated_websites: 'সংযুক্ত ওয়েবসাইটগুলি',
auto_arrange: 'অটো বিন্যাস',
cancel: 'বাতিল করুন',
change_email: 'ই-মেইল পরিবর্তন করুন',
change_language: 'ভাষা পরিবর্তন করুন',
-1
View File
@@ -31,7 +31,6 @@ const br = {
allow: 'Permitir',
ascending: 'Ascendente',
associated_websites: 'Sites Associados',
auto_arrange: 'Organizar Automaticamente',
cancel: 'Cancelar',
change_email: 'Mudar Email',
change_language: 'Mudar Idioma',
-1
View File
@@ -62,7 +62,6 @@ const cs = {
"app_group_remove_from_folder": "Odebrat ze složky",
"associated_websites": "Přidružené weby",
"app_feedback_sent": "Zpětná vazba byla odeslána. Děkujeme!",
"auto_arrange": "Automaticky uspořádat",
"browser_version": "Verze prohlížeče",
"change_language": "Změnit jazyk",
"captcha_required": "Dokončete prosím ověření CAPTCHA",
-1
View File
@@ -31,7 +31,6 @@ const da = {
allow: 'Tillad',
ascending: 'Stigende',
associated_websites: 'Tilknyttede websteder',
auto_arrange: 'Auto Arrangere',
cancel: 'Annuller',
change_email: 'Ændre e-mail',
change_language: 'Ændre sprog',
-1
View File
@@ -31,7 +31,6 @@ const de = {
allow: 'Erlauben',
ascending: 'Aufsteigend',
associated_websites: 'Zugeordnete Webseiten',
auto_arrange: 'Automatisch anordnen',
cancel: 'Abbrechen',
change_email: 'E-Mail ändern',
change_language: 'Sprache ändern',
-1
View File
@@ -26,7 +26,6 @@ const emoji = {
add_another_account: '➕👤',
all_fields_required: '📝🔒✅',
ascending: '🔼',
auto_arrange: '🔄📂📄',
cancel: '❌',
change_language: '🔄🌐',
change_password: '🔑🔄',
-1
View File
@@ -67,7 +67,6 @@ const en = {
app_launch_no_access_unnamed: "You don't have access to this app.",
ascending: 'Ascending',
associated_websites: 'Associated Websites',
auto_arrange: 'Auto Arrange',
back: 'Back',
bio: 'Bio',
browser: 'Browser',
-1
View File
@@ -38,7 +38,6 @@ const es = {
allow: 'Permitir',
ascending: 'Ascendiente',
associated_websites: 'Sitios Web Asociados',
auto_arrange: 'Organización Automática',
cancel: 'Cancelar',
change_email: 'Cambiar Correo Electrónico',
change_language: 'Cambiar Idioma',
-1
View File
@@ -31,7 +31,6 @@ const fa = {
allow: 'اجازه دسترسی',
ascending: 'صعودی',
associated_websites: 'وب سایت های مرتبط',
auto_arrange: 'ترتیب خودکار',
cancel: 'لغو',
change: 'تغییر',
change_always_open_with: 'آیا می‌خواهید همیشه این نوع فایل را با ... باز کنید؟',
-1
View File
@@ -37,7 +37,6 @@ const fi = {
ascending: 'Nouseva',
associated_websites: 'Tähän liittyvät verkkosivustot',
auto_arrange: 'Järjestä automaattisesti',
cancel: 'Peruuta',
change_email: 'Vaihda sähköpostiosoite',
change_language: 'Vaihda kieli',
-1
View File
@@ -31,7 +31,6 @@ const fil = {
allow: 'Payagan',
ascending: 'Pataas',
associated_websites: 'Mga kaugnay na website',
auto_arrange: 'Ayusin nang awtomatiko',
cancel: 'Kanselahin',
change_email: 'Baguhin ang email',
change_language: 'Baguhin ang wika',
-1
View File
@@ -31,7 +31,6 @@ const fr = {
allow: 'Autoriser',
ascending: 'Ascendant',
associated_websites: 'Sites associés',
auto_arrange: 'Organisation automatique',
browser: 'Navigateur',
browser_version: 'Version du navigateur',
captcha_required: 'Veuillez compléter la vérification CAPTCHA',
-1
View File
@@ -31,7 +31,6 @@ const he = {
allow: 'להרשות',
ascending: 'בסדר עולה',
associated_websites: 'אתרים קשורים',
auto_arrange: 'סידור אוטומטי',
cancel: 'ביטול',
change_email: 'שינוי כתובת אימייל',
change_language: 'החלפת שפה',
-1
View File
@@ -31,7 +31,6 @@ const hi = {
allow: 'अनुमति दें',
ascending: 'आरोही',
associated_websites: 'संबंधित वेबसाइटें',
auto_arrange: 'स्वचालित व्यवस्तित',
cancel: 'रद्द',
change_email: 'ई - मेल बदले',
change_language: 'भाषा बदलें',
-1
View File
@@ -31,7 +31,6 @@ const hu = {
allow: 'Engedélyez',
ascending: 'Növekvő',
associated_websites: 'Kapcsolódó weboldalak',
auto_arrange: 'Automatikus elrendezés',
cancel: 'Mégsem',
change_email: 'Email módosítása',
change_language: 'Nyelv módosítása',
-1
View File
@@ -31,7 +31,6 @@ const hy = {
allow: 'Թույլատրել',
ascending: 'Աճող',
associated_websites: 'Կապված կայքեր',
auto_arrange: 'Ավտոմատ դասավորել',
cancel: 'Չեղարկել',
change_email: 'Փոխել էլ. փոստը',
change_language: 'Փոխել լեզուն',
-1
View File
@@ -31,7 +31,6 @@ const id = {
allow: 'Izinkan',
ascending: 'Menaik',
associated_websites: 'Situs Web Terkait',
auto_arrange: 'Atur Otomatis',
cancel: 'Batal',
change_email: 'Ubah Email',
change_language: 'Ubah Bahasa',
-1
View File
@@ -31,7 +31,6 @@ const ig = {
allow: 'ekwe',
ascending: 'Na-arịgo',
associated_websites: 'Weebụsaịtị Ejikọtara',
auto_arrange: 'ndokwa onwe',
cancel: 'Kagbuo',
change_email: 'ịgbanwe email',
change_language: 'ịgbanwe Asụsụ',
-1
View File
@@ -31,7 +31,6 @@ const it = {
allow: 'Consenti',
ascending: 'Ascendente',
associated_websites: 'Siti associati',
auto_arrange: 'Organizzazione automatica',
cancel: 'Annulla',
change_email: 'Modifica Email',
change_language: 'Cambia lingua',
-1
View File
@@ -31,7 +31,6 @@ const ja = {
allow: '許可',
ascending: '昇順',
associated_websites: '関連ウェブサイト',
auto_arrange: '自動配置',
cancel: 'キャンセル',
change_email: 'メールアドレスを変更',
change_language: '言語を変更',
-1
View File
@@ -31,7 +31,6 @@ const ko = {
allow: '허용',
ascending: '오름차순',
associated_websites: '연결된 웹사이트',
auto_arrange: '자동 정렬',
cancel: '취소',
change_email: '이메일 변경',
change_language: '언어 변경',
-1
View File
@@ -31,7 +31,6 @@ const ku = {
allow: 'ڕێگەدان',
ascending: 'سەرووەی',
associated_websites: 'وێبسایتەکان پەیوەندیدار',
auto_arrange: 'ڕیزکردنی ئۆتۆماتیکی',
cancel: 'ڕەتکردنەوە',
change_email: 'گۆڕینی ئیمەیل',
change_language: 'گۆڕینی زمان',
-1
View File
@@ -31,7 +31,6 @@ const ml = {
allow: 'അനുവദിക്കുക',
ascending: 'ആരോഹണം',
associated_websites: 'ബന്ധപ്പെട്ട വെബ്സൈറ്റുകൾ',
auto_arrange: 'സ്വയം ക്രമീകരിക്കുക',
cancel: 'റദ്ദാക്കുക',
change: 'മാറ്റുക',
change_always_open_with: 'ഈ തരത്തിലുള്ള ഫയൽ എപ്പോഴും ഇതുപയോഗിച്ച് തുറക്കണോ',
-1
View File
@@ -38,7 +38,6 @@ const my = {
allow: 'Benarkan',
ascending: 'Menaik',
associated_websites: 'Laman Web Berkaitan',
auto_arrange: 'Susunan Automatik',
cancel: 'Batal',
change: 'Ubah',
change_email: 'Tukar Emel',
-1
View File
@@ -36,7 +36,6 @@ const nb = {
allow: 'Tillate',
ascending: 'Stigende',
associated_websites: 'Tilknyttede nettsider',
auto_arrange: 'Automatisk sortering',
cancel: 'Avbryt',
change: 'Endre',
change_always_open_with: 'Vil du alltid åpne denne filtypen med',
-1
View File
@@ -31,7 +31,6 @@ const nl = {
allow: 'Toestaan',
ascending: 'Oplopend',
associated_websites: 'Geassocieerde Websites',
auto_arrange: 'Automatisch sorteren',
cancel: 'Annuleren',
change_email: 'E-mail Wijzigen',
change_language: 'Taal veranderen',
-1
View File
@@ -146,7 +146,6 @@ const nn = {
account_password: 'Stadfest kontopassord', // In English: "Verify Account Password"
allow: 'Tillat', // In English: "Allow"
associated_websites: 'Tilknytte nettstader', // In English: "Associated Websites"
auto_arrange: 'Auto-ordne', // In English: "Auto Arrange"
change: 'Endre', // In English: "Change"
change_always_open_with: 'Vil du alltid opne denne filtypen med', // In English: "Do you want to always open this type of file with"
change_email: 'Endre e-post', // In English: "Change Email"
-1
View File
@@ -31,7 +31,6 @@ const pl = {
allow: 'Pozwól',
ascending: 'Rosnąco',
associated_websites: 'Powiązane strony',
auto_arrange: 'Auto Aranżacja',
cancel: 'Anuluj',
change_email: 'Zmień email',
change_language: 'Zmień język',
-1
View File
@@ -31,7 +31,6 @@ const pt = {
allow: 'Permitir',
ascending: 'Ascendente',
associated_websites: 'Sites Associados',
auto_arrange: 'Auto Organizar',
cancel: 'Cancelar',
change: 'Alterar',
change_email: 'Alterar e-mail',
-1
View File
@@ -31,7 +31,6 @@ const ro = {
allow: 'Permite',
ascending: 'Ascendent',
associated_websites: 'Site-uri partenere',
auto_arrange: 'Aranjare automată',
cancel: 'Anulează',
change_email: 'Schimbă e-mailul',
change_language: 'Schimbă Limba',
-1
View File
@@ -31,7 +31,6 @@ const ru = {
allow: 'Разрешить',
ascending: 'По возрастанию',
associated_websites: 'Связанные сайты',
auto_arrange: 'Автоупорядочивание',
cancel: 'Отмена',
change_email: 'Изменить электронную почту',
change_language: 'Изменить язык',
-1
View File
@@ -31,7 +31,6 @@ const sl = {
allow: 'Dovoli',
ascending: 'Naraščajoče',
associated_websites: 'Povezana spletna mesta',
auto_arrange: 'Samodejno razporedi',
cancel: 'Prekliči',
change: 'Spremeni',
change_always_open_with: 'Ali želite to vrsto datotek vedno odpirati s/z',
-1
View File
@@ -31,7 +31,6 @@ const sv = {
allow: 'Tillåt',
ascending: 'Stigande',
associated_websites: 'Anknytande webbplatser',
auto_arrange: 'Auto Arrange',
cancel: 'Avbryt',
change_email: 'Ändra e-postadress',
change_language: 'Ändra språk',
-1
View File
@@ -31,7 +31,6 @@ const ta = {
allow: 'அனுமதி',
ascending: 'ஏறுமுகம்',
associated_websites: 'தொடர்புடைய இணையதளங்கள்',
auto_arrange: 'ஆட்டோ ஏற்பாடு',
cancel: 'ரத்து செய்',
change_email: 'மின்னஞ்சலை மாற்றவும்',
change_language: 'மொழியை மாற்றவும்',
-1
View File
@@ -31,7 +31,6 @@ const th = {
allow: 'อนุญาต',
ascending: 'เรียงจากน้อยไปมาก',
associated_websites: 'เว็บไซต์ที่เกี่ยวข้อง',
auto_arrange: 'จัดเรียงอัตโนมัติ',
cancel: 'ยกเลิก',
change_email: 'เปลี่ยนอีเมล',
change_language: 'เปลี่ยนภาษา',
-1
View File
@@ -31,7 +31,6 @@ const tr = {
allow: 'İzin ver',
ascending: 'Artan',
associated_websites: 'İlişkilendirilmiş Web Siteleri',
auto_arrange: 'Otomatik Düzenle',
cancel: 'İptal',
change_email: 'E-postayı Değiştir',
change_language: 'Dili Değiştir',
-1
View File
@@ -31,7 +31,6 @@ const ua = {
allow: 'Дозволити',
ascending: 'За зростанням',
associated_websites: 'Асоційовані веб-сайти',
auto_arrange: 'Автоупорядкування',
cancel: 'Відміна',
change: 'Змінити',
change_email: 'Змінити Email',
-1
View File
@@ -29,7 +29,6 @@ const ur = {
add_another_account: 'دوسرا اکاؤنٹ شامل کریں',
all_fields_required: 'تمام شعبوں کی ضرورت ہے',
ascending: 'بڑھتی ہوئی',
auto_arrange: 'خودکار ترتیب',
cancel: 'منسوخ کریں',
change_email: 'ای میل تبدیل کریں',
change_language: 'زبان تبدیل کریں',
-1
View File
@@ -31,7 +31,6 @@ const vi = {
allow: 'Cho phép',
ascending: 'Tăng dần',
associated_websites: 'Các trang web liên kết',
auto_arrange: 'Tự động sắp xếp',
cancel: 'Hủy',
change_email: 'Thay đổi email',
change_language: 'Thay đổi ngôn ngữ',
-1
View File
@@ -31,7 +31,6 @@ const zh = {
allow: '允许',
ascending: '升序',
associated_websites: '相关网站',
auto_arrange: '自动排序',
cancel: '取消',
change_email: '更改邮箱',
change_language: '更改语言',
-1
View File
@@ -49,7 +49,6 @@ const zhtw = {
app_group_ungroup: '解除群組',
ascending: '升序',
associated_websites: '關聯的網站',
auto_arrange: '自動排列',
cancel: '取消',
change_email: '更改電子郵件',
change_language: '更改語言',
+5 -15
View File
@@ -274,15 +274,7 @@ const postAuthActions = async (action) => {
// Load desktop, only if we're not embedded in a popup and not on the dashboard page
// -------------------------------------------------------------------------------------
if ( !window.embedded_in_popup && !window.is_dashboard_mode ) {
if ( window.is_fullpage_mode ) {
// In fullpage mode, skip loading desktop items and background
UIDesktop({});
} else {
await window.get_auto_arrange_data();
puter.fs.stat({ path: window.desktop_path, consistency: 'eventual' }).then(desktop_fsentry => {
UIDesktop({ desktop_fsentry: desktop_fsentry });
});
}
UIDesktop();
}
// -------------------------------------------------------------------------------------
// Dashboard mode
@@ -1261,7 +1253,7 @@ if (jQuery) {
// are we in dashboard mode?
// The dashboard is the default interface at the root path; `/dashboard` is kept as an
// alias, and `/desktop` loads the desktop instead. Direct app landings (`/app/<name>`)
// alias. Direct app landings (`/app/<name>`)
// open in the dashboard too: the app comes up maximized in-page with the dashboard
// route slotted underneath (see postAuthActions), so Back minimizes to the dashboard.
// URLs that carry a desktop-only flow keep booting the desktop: auth popups
@@ -2274,8 +2266,8 @@ window.initgui = async function (options) {
sessionStorage.removeItem('puter.authme_redirect_attempted');
} catch (e) { /* sessionStorage unavailable */ }
}
// remove the token from URL, keeping the current path (e.g. `/` or
// `/desktop`) and hash (dashboard tab links like /#usage).
// remove the token from URL, keeping the current path and hash
// (dashboard tab links like /#usage).
//
// `replaceState`, not `pushState`: pushing leaves the token-bearing URL
// as the previous history entry, so Back returns to a URL containing a
@@ -2415,9 +2407,7 @@ window.initgui = async function (options) {
$('.desktop').remove();
// disable native browser exit confirmation
window.onbeforeunload = null;
// go back to the interface the user was in: dashboard users to the root
// dashboard, desktop users to /desktop
window.location.replace(window.is_dashboard_mode ? '/' : '/desktop');
window.location.replace('/');
});
$(document).ajaxError(async function (event, jqxhr) {
+2
View File
@@ -335,6 +335,8 @@ describe('kv.get GUI boot cache', () => {
expect(lastBody().args.key).not.toContain('menubar_style');
expect(lastBody().args.key).not.toContain('user_preferences.clock_visible');
expect(lastBody().args.key).not.toContain('has_set_default_app_user_permissions');
expect(lastBody().args.key).not.toContain('user_preferences.auto_arrange_desktop');
expect(lastBody().args.key).not.toContain('desktop_item_positions');
});
it('a failed batch makes get(bootKey) issue a normal call', async () => {
@@ -6,10 +6,8 @@ import { driverCallEnvelope } from '../../../lib/networkUtils.js';
const GUI_CACHE_KEYS = [
'window_sidebar_width',
'sidebar_items',
'user_preferences.auto_arrange_desktop',
'user_preferences.show_hidden_files',
'user_preferences.language',
'desktop_item_positions',
];
// How long the resolved batch keeps serving reads before the cache disables
+6 -6
View File
@@ -880,9 +880,9 @@ export default suite('kv', {
// The GUI batch-reads a fixed set of keys while it boots. No runner is
// the GUI, so here those names are ordinary keys.
'get of a GUI boot key returns the stored value': async (t) => {
await t.puter.kv.set('user_preferences.auto_arrange_desktop', 'true');
t.assert.equal(await t.puter.kv.get('user_preferences.auto_arrange_desktop'), 'true');
t.assert.equal(await t.puter.kv.get('user_preferences.auto_arrange_desktop'), 'true');
await t.puter.kv.set('user_preferences.show_hidden_files', 'true');
t.assert.equal(await t.puter.kv.get('user_preferences.show_hidden_files'), 'true');
t.assert.equal(await t.puter.kv.get('user_preferences.show_hidden_files'), 'true');
t.assert.equal(
await t.puter.kv.get('window_sidebar_width'),
null,
@@ -892,9 +892,9 @@ export default suite('kv', {
'writes to a GUI boot key are visible to the next get': async (t) => {
await t.puter.kv.set('sidebar_items', '[]');
t.assert.equal(await t.puter.kv.get('desktop_item_positions'), null);
await t.puter.kv.set('desktop_item_positions', '{}');
t.assert.equal(await t.puter.kv.get('desktop_item_positions'), '{}');
t.assert.equal(await t.puter.kv.get('window_sidebar_width'), null);
await t.puter.kv.set('window_sidebar_width', '240');
t.assert.equal(await t.puter.kv.get('window_sidebar_width'), '240');
await t.puter.kv.del('sidebar_items');
t.assert.equal(await t.puter.kv.get('sidebar_items'), null);
},