feat: add copy mode save

Adds a 'copy' mode to showSaveFilePicker. In this mode, a source file is
copied to the target file. The file may not exist in another apps
AppData directory. Unlike the 'move' mode, the file can be outside of
the app's AppData directory (a "user" file); in this situation the user
will be prompted to grant permission for the copy action. The app will
have access to the newly-created copy.
This commit is contained in:
KernelDeimos
2025-08-15 09:48:37 -07:00
committed by Eric Dubé
parent 483ba14af8
commit 6b0865e284
2 changed files with 55 additions and 17 deletions
+52 -15
View File
@@ -1382,32 +1382,68 @@ const ipc_listener = async (event, handled) => {
$(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide();
};
const handle_move_save = async ({ source_path, target_path, el_filedialog_window }) => {
const handle_move_save = async ({
// when 'source_path' has a value, 'save_type' is checked to determine
// if a fs.move() or fs.copy() needs to be performed.
save_type,
source_path, target_path, el_filedialog_window,
}) => {
// source path must be in appdata directory
const stat_info = await puter.fs.stat(source_path);
if ( ! stat_info.appdata_app ) {
await puter.ui.alert(`the app ${app_uuid} attempted to ` +
`move data owned by the user illegaly`);
return;
if ( ! stat_info.appdata_app || stat_info.appdata_app !== app_uuid ) {
const source_file_owner = stat_info?.appdata_app ?? 'the user';
if ( stat_info.appdata_app && stat_info.appdata_app !== app_uuid ) {
await UIAlert({
message: `apps are prohibited from accessing AppData of other apps`
});
return;
}
if ( save_type === 'move' ) {
await UIAlert({
message: `the app <b>${app_name}</b> tried to illegally move a file owned by ${source_file_owner}`,
});
return;
}
const alert_resp = await UIAlert({
message: `the app ${app_name} is trying to copy ${source_path}; is this okay?`,
buttons: [
{
label: i18n('yes'),
value: true,
type: 'primary',
},
{
label: i18n('no'),
value: false,
type: 'secondary',
},
]
});
// `alert_resp` will be `"false"`, but this check is forward-compatible
// with a version of UIAlert that returns `false`.
if ( ! alert_resp || alert_resp === 'false' ) return;
}
if ( stat_info.appdata_app !== app_uuid ) {
await puter.ui.alert(`the app ${app_uuid} attempted to ` +
`move data owned by ${stat_info.appdata_app}`);
return;
}
console.log('supposedly we\'re writing this file now');
let node;
const written = await window.handle_same_name_exists({
action: async ({ overwrite }) => {
if ( overwrite ) {
await puter.fs.delete(target_path);
}
await puter.fs.move(source_path, target_path);
if ( save_type === 'copy' ) {
const target_dir = path.dirname(target_path);
const new_name = path.basename(target_path);
await puter.fs.copy(source_path, target_dir, {
newName: new_name,
});
} else {
await puter.fs.move(source_path, target_path);
}
node = await puter.fs.stat(target_path);
console.log('the move operation just happened', { node });
},
parent_uuid: $(el_filedialog_window).attr('data-element_uuid'),
});
@@ -1441,6 +1477,7 @@ const ipc_listener = async (event, handled) => {
done = await handle_url_save({ target_path });
} else if ( event.data.source_path ) {
done = await handle_move_save({
save_type: event.data.save_type,
source_path: event.data.source_path,
target_path,
});
+3 -2
View File
@@ -735,13 +735,14 @@ class UI extends EventListener {
type = 'url';
}
const url = type === 'url' ? content.toString() : undefined;
const source_path = type === 'move' ? content : undefined;
const source_path = ['move','copy'].includes(type) ? content : undefined;
if(this.env === 'app'){
this.messageTarget?.postMessage({
msg: "showSaveFilePicker",
appInstanceID: this.appInstanceID,
content: url ? undefined : content,
save_type: type,
url,
source_path,
suggestedName: suggestedName ?? '',