Problem with Undo and File

Writing an InDesign UXP plugin I want to perform a file-related action and wrap that in one undo step. Unfortunately, as soon as I create the file object, the undo recording stops. The function continues to run and works fine, but all steps in the layout after the creation of the file object get their own undo step, which is annoying for the user (and possibly impacting performance).

Simplified version of my code:

const indesign = require("indesign"),
    app = indesign.app,
    uxpStorage = require('uxp').storage,
    fs = uxpStorage.localFileSystem;

app.doScript(placeImage.bind(this, imgSrcUrl, page), indesign.ScriptLanguage.UXPSCRIPT, 
				 undefined, indesign.UndoModes.ENTIRE_SCRIPT, "place an image");

async function placeImage(imgSrcUrl, page) {
    const frame = page.rectangles.add( ... );   // Rectangle constructor
    frame.applyObjectStyle(obStyle, true);     // obStyle: my preferred ObjectStyle

    const resp = await fetch(imgSrcUrl);
    const buffer = await resp.arrayBuffer();

    const tmpFile = await tmpFolder.createEntry(fileName, {overwrite: true});
        // from now on, all layout-related actions get their own undo step
    const bytesWritten = await tmpFile.write(buffer, {format: uxpStorage.formats.binary});

    frame.place(tmpFile);
    frame.fit(indesign.FitOptions.PROPORTIONALLY)
    frame.fit(indesign.FitOptions.FRAME_TO_CONTENT)
} 

Using the fs module to write the file, this does not occur, InDesign keeps recording the undo action. But for frame.place I need a File object.

For me, this looks like a bug. Any ideas or workarounds?

I just saw you reference this on another ticket.

I’m using .place to place files that I’m not referencing via .doScript

		let snippetFileToPlace_f = await folderForSnippetAndMetadataFiles_d.createFile(snippetFileAndExtension_s, { overwrite: true, format: g_uxp_o.storage.formats.binary });

That creates the file object. And then later, in a separate function, I write to it from a download (it has a different name here):

		let arrayBuffer_ab = await rawWebServiceResponse_o.arrayBuffer();
		if (arrayBuffer_ab.byteLength == 0) {

			g_logSystem_c.wLog(`Failed to read the file: '${fullURL_s}'.`);

			return false;

		}

		//-- Write the file and check it's length
		if (await destination_f.write(arrayBuffer_ab, { format: g_uxp_o.storage.formats.binary }) === 0) {

			g_logSystem_c.wLog(`Failed to write the file: '${destination_f.name}'.`);

			return false;

		}

And .place succeeds in native UXP

Never used bind, but looked it up as @medium_jon just bumped this thread.

The result of placeImage.bind is a function, lets call it bound.

When invoked by doScript, bound will call your placeImage with the given this and additional parameters.

As your placeImage() is async, its execution will be deferred and the call immediately returns to bound, which then is done. As is your call to doScript.

Actual execution of your async placeImage then continues whenever async handling feels like that, outside the ENTIRE_SCRIPT wrapper as you found out.

Not sure why you need to make placeImage async - is it the inside async file operations? Maybe. I’d use Sync functions for your I/O – if those are available in UXP, or await until everything async is settled, and only then wrap all the InDesign steps for your frame into a separate, following function. Invoke only that new function with doScript. You can’t undo the fetch or file write anyway.

Turns out pretty similar to the order suggested by Jon.

Thank you @Dirk for your suggestions! Makes sense… I haven’t found sync file i/o functions in UXP, so probably I’ll have to go the other way, but that should be possible.