mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-13 20:12:57 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a299872bcc | ||
|
|
d374cdb04f | ||
|
|
2d13b73b04 |
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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"');
|
||||
});
|
||||
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: () => {},
|
||||
|
||||
@@ -31,7 +31,6 @@ const ar = {
|
||||
allow: 'السماح',
|
||||
ascending: 'تصاعدي',
|
||||
associated_websites: 'المواقع المرتبطة',
|
||||
auto_arrange: 'ترتيب تلقائي',
|
||||
cancel: 'إلغاء',
|
||||
change_email: 'تغيير البريد الإلكتروني',
|
||||
change_language: 'تغيير اللغة',
|
||||
|
||||
@@ -31,7 +31,6 @@ const bg = {
|
||||
allow: 'Разреши',
|
||||
ascending: 'Възходящ',
|
||||
associated_websites: 'Свързани уебсайтове',
|
||||
auto_arrange: 'Автоматично подреждане',
|
||||
cancel: 'Отказ',
|
||||
change: 'Промени',
|
||||
change_always_open_with: 'Искате ли винаги да отваряте този тип файл с',
|
||||
|
||||
@@ -31,7 +31,6 @@ const bn = {
|
||||
allow: 'অনুমতি দিন',
|
||||
ascending: 'ঊর্ধ্বক্রমে',
|
||||
associated_websites: 'সংযুক্ত ওয়েবসাইটগুলি',
|
||||
auto_arrange: 'অটো বিন্যাস',
|
||||
cancel: 'বাতিল করুন',
|
||||
change_email: 'ই-মেইল পরিবর্তন করুন',
|
||||
change_language: 'ভাষা পরিবর্তন করুন',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -26,7 +26,6 @@ const emoji = {
|
||||
add_another_account: '➕👤',
|
||||
all_fields_required: '📝🔒✅',
|
||||
ascending: '🔼',
|
||||
auto_arrange: '🔄📂📄',
|
||||
cancel: '❌',
|
||||
change_language: '🔄🌐',
|
||||
change_password: '🔑🔄',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,6 @@ const fa = {
|
||||
allow: 'اجازه دسترسی',
|
||||
ascending: 'صعودی',
|
||||
associated_websites: 'وب سایت های مرتبط',
|
||||
auto_arrange: 'ترتیب خودکار',
|
||||
cancel: 'لغو',
|
||||
change: 'تغییر',
|
||||
change_always_open_with: 'آیا میخواهید همیشه این نوع فایل را با ... باز کنید؟',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,6 @@ const he = {
|
||||
allow: 'להרשות',
|
||||
ascending: 'בסדר עולה',
|
||||
associated_websites: 'אתרים קשורים',
|
||||
auto_arrange: 'סידור אוטומטי',
|
||||
cancel: 'ביטול',
|
||||
change_email: 'שינוי כתובת אימייל',
|
||||
change_language: 'החלפת שפה',
|
||||
|
||||
@@ -31,7 +31,6 @@ const hi = {
|
||||
allow: 'अनुमति दें',
|
||||
ascending: 'आरोही',
|
||||
associated_websites: 'संबंधित वेबसाइटें',
|
||||
auto_arrange: 'स्वचालित व्यवस्तित',
|
||||
cancel: 'रद्द',
|
||||
change_email: 'ई - मेल बदले',
|
||||
change_language: 'भाषा बदलें',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,6 @@ const hy = {
|
||||
allow: 'Թույլատրել',
|
||||
ascending: 'Աճող',
|
||||
associated_websites: 'Կապված կայքեր',
|
||||
auto_arrange: 'Ավտոմատ դասավորել',
|
||||
cancel: 'Չեղարկել',
|
||||
change_email: 'Փոխել էլ. փոստը',
|
||||
change_language: 'Փոխել լեզուն',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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ụ',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,6 @@ const ja = {
|
||||
allow: '許可',
|
||||
ascending: '昇順',
|
||||
associated_websites: '関連ウェブサイト',
|
||||
auto_arrange: '自動配置',
|
||||
cancel: 'キャンセル',
|
||||
change_email: 'メールアドレスを変更',
|
||||
change_language: '言語を変更',
|
||||
|
||||
@@ -31,7 +31,6 @@ const ko = {
|
||||
allow: '허용',
|
||||
ascending: '오름차순',
|
||||
associated_websites: '연결된 웹사이트',
|
||||
auto_arrange: '자동 정렬',
|
||||
cancel: '취소',
|
||||
change_email: '이메일 변경',
|
||||
change_language: '언어 변경',
|
||||
|
||||
@@ -31,7 +31,6 @@ const ku = {
|
||||
allow: 'ڕێگەدان',
|
||||
ascending: 'سەرووەی',
|
||||
associated_websites: 'وێبسایتەکان پەیوەندیدار',
|
||||
auto_arrange: 'ڕیزکردنی ئۆتۆماتیکی',
|
||||
cancel: 'ڕەتکردنەوە',
|
||||
change_email: 'گۆڕینی ئیمەیل',
|
||||
change_language: 'گۆڕینی زمان',
|
||||
|
||||
@@ -31,7 +31,6 @@ const ml = {
|
||||
allow: 'അനുവദിക്കുക',
|
||||
ascending: 'ആരോഹണം',
|
||||
associated_websites: 'ബന്ധപ്പെട്ട വെബ്സൈറ്റുകൾ',
|
||||
auto_arrange: 'സ്വയം ക്രമീകരിക്കുക',
|
||||
cancel: 'റദ്ദാക്കുക',
|
||||
change: 'മാറ്റുക',
|
||||
change_always_open_with: 'ഈ തരത്തിലുള്ള ഫയൽ എപ്പോഴും ഇതുപയോഗിച്ച് തുറക്കണോ',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,6 @@ const ru = {
|
||||
allow: 'Разрешить',
|
||||
ascending: 'По возрастанию',
|
||||
associated_websites: 'Связанные сайты',
|
||||
auto_arrange: 'Автоупорядочивание',
|
||||
cancel: 'Отмена',
|
||||
change_email: 'Изменить электронную почту',
|
||||
change_language: 'Изменить язык',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,6 @@ const ta = {
|
||||
allow: 'அனுமதி',
|
||||
ascending: 'ஏறுமுகம்',
|
||||
associated_websites: 'தொடர்புடைய இணையதளங்கள்',
|
||||
auto_arrange: 'ஆட்டோ ஏற்பாடு',
|
||||
cancel: 'ரத்து செய்',
|
||||
change_email: 'மின்னஞ்சலை மாற்றவும்',
|
||||
change_language: 'மொழியை மாற்றவும்',
|
||||
|
||||
@@ -31,7 +31,6 @@ const th = {
|
||||
allow: 'อนุญาต',
|
||||
ascending: 'เรียงจากน้อยไปมาก',
|
||||
associated_websites: 'เว็บไซต์ที่เกี่ยวข้อง',
|
||||
auto_arrange: 'จัดเรียงอัตโนมัติ',
|
||||
cancel: 'ยกเลิก',
|
||||
change_email: 'เปลี่ยนอีเมล',
|
||||
change_language: 'เปลี่ยนภาษา',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,6 @@ const ua = {
|
||||
allow: 'Дозволити',
|
||||
ascending: 'За зростанням',
|
||||
associated_websites: 'Асоційовані веб-сайти',
|
||||
auto_arrange: 'Автоупорядкування',
|
||||
cancel: 'Відміна',
|
||||
change: 'Змінити',
|
||||
change_email: 'Змінити Email',
|
||||
|
||||
@@ -29,7 +29,6 @@ const ur = {
|
||||
add_another_account: 'دوسرا اکاؤنٹ شامل کریں',
|
||||
all_fields_required: 'تمام شعبوں کی ضرورت ہے',
|
||||
ascending: 'بڑھتی ہوئی',
|
||||
auto_arrange: 'خودکار ترتیب',
|
||||
cancel: 'منسوخ کریں',
|
||||
change_email: 'ای میل تبدیل کریں',
|
||||
change_language: 'زبان تبدیل کریں',
|
||||
|
||||
@@ -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ữ',
|
||||
|
||||
@@ -31,7 +31,6 @@ const zh = {
|
||||
allow: '允许',
|
||||
ascending: '升序',
|
||||
associated_websites: '相关网站',
|
||||
auto_arrange: '自动排序',
|
||||
cancel: '取消',
|
||||
change_email: '更改邮箱',
|
||||
change_language: '更改语言',
|
||||
|
||||
@@ -49,7 +49,6 @@ const zhtw = {
|
||||
app_group_ungroup: '解除群組',
|
||||
ascending: '升序',
|
||||
associated_websites: '關聯的網站',
|
||||
auto_arrange: '自動排列',
|
||||
cancel: '取消',
|
||||
change_email: '更改電子郵件',
|
||||
change_language: '更改語言',
|
||||
|
||||
+5
-15
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user