Progress bar for InDesign interactions

,

I have now created a simple plugin to demonstrate the problem with the progress bar mentioned in the office hours last week. It is about displaying progress during Indesign interactions.

progressbar-plugin.ccx (81.7 KB)

sp-progressbar works with setInterval, but that is quite cumbersome – upper progress bar in the plug-in.

In the progress bar below, a new InDesign document is created and a few text frames are created (number of text frames in the input field). While InDesign is busy, the progress bar is not updated.

It would be nice if there was a »re-render method« or the panel updated itself during the interaction with InDesign. I hope I haven’t overlooked anything or implemented it incorrectly, otherwise please let me know.

Roland

When I first read your post, I thought that there must be a solution, especially as we can now run asynchronous code, but I couldn’t get it to work until I discovered that if I put a delay (even of a millisecond) on the Resolve callback of a Promise, the ui would redraw (and therefore the progress bar would update).

This is the critical piece of code:

	return new Promise(function (resolve, reject) {
		// do whatever . . .
		setTimeout(function () {
			resolve();
		}, 1);
	});

To be honest, I don’t know why this works, but it does, and I think it gives us a simple way to update the progress bar as and when we need to.

As a proof of concept, I have made a test plugin.

Because we want the ui to redraw when we update the progress bar I have two functions, one to reset the progress bar (set its value to zero) and one to increment the progress bar (advance its value). Each function wraps the setting of the progress bar’s value in a Promise which includes the ‘Resolve trigger’.

const progressBar = document.getElementById('progress-bar');

function resetProgress() {
	return new Promise(function (resolve, reject) {
		progressBar.value = 0;
		setTimeout(function () { // millisecond pause triggers refresh of ui
			resolve();
		}, 1);
	});
}

function incrementProgress() {
	return new Promise(function (resolve, reject) {
		progressBar.value = progressBar.value + 1;
		setTimeout(function () { // millisecond pause triggers refresh of ui
			resolve();
		}, 1);
	});
}

I have three buttons: ‘promise’, ‘async’ and ‘loop’. Following your dummy plugin, clicking on any one of my three buttons has InDesign create a new document and put a text frame on the first page.

Then, to simulate a long-running process, I have a little function that in a loop moves an object (in this case the text frame) one unit to the right and then one unit to the left (i.e. back to where it started). Completely useless in the real world, but when looping about 250 times it keeps InDesign occupied long enough for us to see the progress bar move slowly.

function slowMove(obj, numMoves) {
	for (let i = 0; i < numMoves; i++) {
		obj.move([1,0]);
		obj.move([-1,0]);
	}
}

The button ‘promise’ shows how this all comes together with a Promise chain:

resetProgress()
.then(function() {
	slowMove(textFrame, numMoves);
	return incrementProgress();
}).then(function() {
	slowMove(textFrame, numMoves);
	return incrementProgress();
}).then(function() {
	slowMove(textFrame, numMoves);
	return incrementProgress();
}).then(function() {
	slowMove(textFrame, numMoves);
	return incrementProgress();
}).then(function() {
	slowMove(textFrame, numMoves);
	return incrementProgress();
});

The progress bar moves to 20%, 40%, 60%, 80%, 100% when each incrementProgress() is reached.

The button ‘async’ shows how this can be used with async/await syntax (note that with async/await the actions are all put in an asynchronous function called from the ‘click’ handler).

async function run(obj, numMoves) {
	await resetProgress();
	slowMove(obj, numMoves);
	await incrementProgress();
	slowMove(obj, numMoves);
	await incrementProgress();
	slowMove(obj, numMoves);
	await incrementProgress();
	slowMove(obj, numMoves);
	await incrementProgress();
	slowMove(obj, numMoves);
	await incrementProgress();
}

The progress bar moves as it does when the ‘promise’ button is clicked.

In practice, a progress bar is often combined with code performed in a loop, so the ‘loop’ button shows how this might be done.

In the ‘click’ handler:

		let numMoves = 10;
		setProgressMax(100);
		runLoop(textFrame, numMoves);

Calling this function:

async function runLoop(obj, numMoves) {
	let numLoops = progressBar.max;
	await resetProgress();
	for (let i = 0; i < numLoops; i++) {
		slowMove(obj, numMoves);
		await incrementProgress();
	}
}

Our slowMove() function (in this case only moving the text frame back and forth 10 times) is run 100 times and the progress bar progresses (quickly) from 0% to 100%.

I am attaching the plugin for anyone who might want to see the whole code. Any suggestions for improvement welcomed.

progress-bar-2976637_ID.ccx (13.2 KB)

Philip

2 Likes

Thanks @philipbuckley for the effort that took!

Hi Philip,

thanks for the further investigation. It is also my assumption that a re-render only takes place after each task, i.e. via setTimeout or setInterval:

   const { app } = indesign;
   const progressElem = document.getElementById("dummy-progressbar");
   const doc = app.documents.add();
   const textFrame = doc.pages.item(0).textFrames.add();

   const max = 5;
   const numMoves = 50;
   let n = 0;
   const timer = setInterval(() => {
   	n += 1;
   	slowMove(textFrame, numMoves);
   	progressElem.value = n;
   	if(n === max) {
   		clearInterval(timer);
   	}
   }, 0);

For me here (macOS) your code also works with 0 ms in setTimeout. But I think I had already observed a similar strange behavior as yours with setTimeout.

But I nevertheless think that progressbar should work simpler.

Roland

Hi Roland,

Yes. You had already identified setInterval() as a way to achieve a moving progress bar, so the code I offered wasn’t a material advancement on yours – like you, I can’t find a way of advancing the progress bar without setInterval or setTimeout. The one thing that did please about my final code was that, by putting the setTimeout trigger into the functions for moving the progress bar, the code for performing the sequence of actions was left clean and readable – just an aesthetic thing.

Philip