mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-13 23:29:50 +00:00
Dashboard Files: don't offer to move items the user can't move
Trashing or cutting sends an item to its owner's trash or a new folder, which a shared root or a read-only share can't do — the server answers Forbidden and the user gets an error alert for an action that should not have been offered. The single-item context menu already leaves Delete out for such rows; the Delete key, Ctrl/Cmd+X, the multi-selection menu and the mobile selection bar did not check. Route all of them through can_restructure, skipping rows that can't move and hiding Cut/Delete when the whole selection can't.
This commit is contained in:
1 parent
1ea57cf4a2
commit
09552201ff
1 file changed
+73
-30
@@ -40,7 +40,7 @@ import { isEntryVisible, isHiddenName, showHiddenFiles } from './hiddenFiles.js'
|
||||
|
||||
import { icons } from '../../helpers/actionIcons.js';
|
||||
import list_all_shared from '../../helpers/listAllShared.js';
|
||||
import { can_share, remember_shared_roots } from '../../helpers/sharedAccess.js';
|
||||
import { can_restructure, can_share, remember_shared_roots } from '../../helpers/sharedAccess.js';
|
||||
import { parent_path_for, shared_crumbs_for, shared_uids_from_paths } from '../../helpers/sharePaths.js';
|
||||
|
||||
const { html_encode, SelectionArea } = window;
|
||||
@@ -796,8 +796,14 @@ const TabFiles = {
|
||||
await window.refresh_trash_state();
|
||||
}
|
||||
} else {
|
||||
// Move to trash
|
||||
await window.move_items($selectedRows.toArray(), window.trash_path);
|
||||
// Trashing sends an item to its owner's trash, which a
|
||||
// shared root or read-only share can't do (the server
|
||||
// answers Forbidden) — skip those rows, as the context
|
||||
// menu leaves Delete out for them.
|
||||
const rows = await _this.restructurableRows($selectedRows.toArray());
|
||||
if ( rows.length > 0 ) {
|
||||
await window.move_items(rows, window.trash_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -841,14 +847,19 @@ const TabFiles = {
|
||||
if ( $selectedRows.length > 0 ) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.clipboard = [];
|
||||
window.clipboard_op = 'move';
|
||||
$selectedRows.each(function () {
|
||||
window.clipboard.push({
|
||||
path: $(this).attr('data-path'),
|
||||
uid: $(this).attr('data-uid'),
|
||||
});
|
||||
});
|
||||
// Cutting is a move: rows that can't be moved (see the
|
||||
// Delete key above) would only fail on paste.
|
||||
const rows = await _this.restructurableRows($selectedRows.toArray());
|
||||
if ( rows.length > 0 ) {
|
||||
window.clipboard = [];
|
||||
window.clipboard_op = 'move';
|
||||
for ( const row of rows ) {
|
||||
window.clipboard.push({
|
||||
path: $(row).attr('data-path'),
|
||||
uid: $(row).attr('data-uid'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1451,11 +1462,12 @@ const TabFiles = {
|
||||
});
|
||||
|
||||
// Cut button
|
||||
$actions.find('.cut-btn').on('click', function () {
|
||||
const selectedRows = document.querySelectorAll('.files-tab .row.selected');
|
||||
$actions.find('.cut-btn').on('click', async function () {
|
||||
const rows = await _this.restructurableRows(document.querySelectorAll('.files-tab .row.selected'));
|
||||
if ( rows.length === 0 ) return;
|
||||
window.clipboard_op = 'move';
|
||||
window.clipboard = [];
|
||||
selectedRows.forEach(row => {
|
||||
rows.forEach(row => {
|
||||
window.clipboard.push({
|
||||
path: $(row).attr('data-path'),
|
||||
uid: $(row).attr('data-uid'),
|
||||
@@ -1498,7 +1510,10 @@ const TabFiles = {
|
||||
await window.refresh_trash_state();
|
||||
}
|
||||
} else {
|
||||
window.move_items(Array.from(selectedRows), window.trash_path);
|
||||
const rows = await _this.restructurableRows(selectedRows);
|
||||
if ( rows.length > 0 ) {
|
||||
window.move_items(rows, window.trash_path);
|
||||
}
|
||||
}
|
||||
$actions.removeClass('visible');
|
||||
});
|
||||
@@ -1551,9 +1566,30 @@ const TabFiles = {
|
||||
if ( this._shareCheckToken !== token ) return;
|
||||
$actions.find('.share-btn').toggle(may_share);
|
||||
});
|
||||
// Cut and Delete move items, which a shared root or a read-only
|
||||
// share can't be — same wait-for-the-answer treatment as Share.
|
||||
$actions.find('.cut-btn, .delete-btn').hide();
|
||||
this.restructurableRows(selectedRows).then((rows) => {
|
||||
if ( this._shareCheckToken !== token ) return;
|
||||
$actions.find('.cut-btn, .delete-btn').toggle(rows.length === selectedRows.length);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The rows the user may move or trash: their own items, plus items inside
|
||||
* a shared folder they hold write on. A shared root or a read-only share
|
||||
* stays where its owner put it (see can_restructure).
|
||||
*
|
||||
* @param {NodeList|Array<HTMLElement>} rows - The selected row elements
|
||||
* @returns {Promise<HTMLElement[]>}
|
||||
*/
|
||||
async restructurableRows (rows) {
|
||||
const list = Array.from(rows);
|
||||
const answers = await Promise.all(list.map((row) => can_restructure($(row).attr('data-path'))));
|
||||
return list.filter((_row, i) => answers[i]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether every selected row may be shared with someone else. A selection
|
||||
* mixing your own items with someone else's read-only ones can't be, and
|
||||
@@ -4147,20 +4183,27 @@ const TabFiles = {
|
||||
items.push('-');
|
||||
}
|
||||
|
||||
// Cut and Delete move items, which a shared root or a read-only share
|
||||
// can't be (the server answers Forbidden) — offered only when the whole
|
||||
// selection may move, as the single-item menu does.
|
||||
const mayRestructure = (await _this.restructurableRows(selectedRows)).length === selectedRows.length;
|
||||
|
||||
// Cut
|
||||
items.push({
|
||||
html: `${i18n('cut')}`,
|
||||
onClick: function () {
|
||||
window.clipboard_op = 'move';
|
||||
window.clipboard = [];
|
||||
selectedRows.forEach(row => {
|
||||
window.clipboard.push({
|
||||
path: $(row).attr('data-path'),
|
||||
uid: $(row).attr('data-uid'),
|
||||
if ( mayRestructure ) {
|
||||
items.push({
|
||||
html: `${i18n('cut')}`,
|
||||
onClick: function () {
|
||||
window.clipboard_op = 'move';
|
||||
window.clipboard = [];
|
||||
selectedRows.forEach(row => {
|
||||
window.clipboard.push({
|
||||
path: $(row).attr('data-path'),
|
||||
uid: $(row).attr('data-uid'),
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Copy
|
||||
if ( ! anyTrashed ) {
|
||||
@@ -4176,10 +4219,9 @@ const TabFiles = {
|
||||
});
|
||||
}
|
||||
|
||||
items.push('-');
|
||||
|
||||
// Delete
|
||||
if ( anyTrashed ) {
|
||||
items.push('-');
|
||||
items.push({
|
||||
html: i18n('delete_permanently'),
|
||||
onClick: async function () {
|
||||
@@ -4199,7 +4241,8 @@ const TabFiles = {
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
else if ( mayRestructure ) {
|
||||
items.push('-');
|
||||
items.push({
|
||||
html: `${i18n('delete')}`,
|
||||
onClick: function () {
|
||||
|
||||
Reference in new issue
Block a user