OfficeDev / office-js

A repo and NPM package for Office.js, corresponding to a copy of what gets published to the official "evergreen" Office.js CDN, at https://appsforoffice.microsoft.com/lib/1/hosted/office.js.
https://learn.microsoft.com/javascript/api/overview
Other
685 stars 95 forks source link

Cannot insert image in web PowerPoint if another image is selected, but cannot unselect the image #3698

Open ghaskins99 opened 1 year ago

ghaskins99 commented 1 year ago

Your Environment

Expected behavior

calling setSelectedDataAsync should not fail if the user has selected something; or there should be an alternative which inserts rather than attempting to change the selection as setSelectedDataAsync seems to be currently the only way to insert an image into PPT

calling slide.setSelectedShapes([]); should clear the selection

Current behavior

calling setSelectedDataAsync throws OSF.DDA.Error 2001

calling slide.setSelectedShapes([]); does nothing and appears to halt the PowerPoint.run callback, ie. no other statements after that appear to run, but there is no error message.

Steps to reproduce

  1. insert a random image into the ppt slide (and leave it selected)
  2. use setSelectedDataAsync to insert base64 image data with coercion type Image, ex.:
    return new Promise((resolve, reject) => {
        Office.context.document.setSelectedDataAsync(
            base64Data,
            {
                coercionType: Office.CoercionType.Image,
            },
            async (result) => {
                if (result.status === Office.AsyncResultStatus.Failed) {
                    reject(result.error.message);
                }
                return resolve(true);
            },
        );
    });
  3. image is not inserted, see in the console:
    OSF.DDA.Error {
    name: 'Data Write Error',
    message: 'Cannot write to the current selection.',
    code: 2001
    }

this would not be an issue if we could programatically unselect, but for that:

  1. insert a random image into the ppt slide (and leave it selected)
  2. use code such as: (logging added for purposes of this issue)

    const example = async (slideIndex) => {
    await PowerPoint.run(async (context) => {
        const slides = context.presentation.slides;
        slides.load('items');
        await context.sync();
        const slide = slides.items[slideIndex]; // ex., 0 for a single-slide file
    
        const selected = context.presentation.getSelectedShapes();
        selected.load('items');
        await context.sync();
        console.log('selected', selected.items);
    
        slide.setSelectedShapes([]);
        await context.sync();
    
        console.log('before get selected');
        const selectedAfter = context.presentation.getSelectedShapes();
        console.log('after get selected');
    
        selectedAfter.load('items');
        console.log('after load, before sync');
    
        // nothing after here
        await context.sync();
        console.log('after sync');
    
        console.log('selectedAfter', selectedAfter.items);
    });
    
    console.log("outside of ppt run")
    }
  3. see that the first log reports the array of selected items (screenshot)
  4. see that after setting the selection, it is not cleared, and loading the selection then seems to never resolve; ie., code after the ppt.run function does not run and no errors are logged or thrown image

Context

Our addin allows users to insert a visual representation of a live file as an image into their PPT slide. If the backing file changes we would like to allow the users to update the image representation by selecting the image and clicking "update" from the task pane addin, which renders the latest version as an image, places itself where the old image was, and deletes the old image (since as far as I can tell we cannot update the existing image's contents).

Being unable to setSelectedDataAsync while an image is selected breaks this functionality since the image must be selected in the first place in order to have the option to update it appear in the task pane, which also shows other relevant information about the file).

We also believe this (the setSelectedDataAsync error) is a regression since I am somewhere between 75-99% sure that this update flow (ie. inserting an image while one is selected) was verified to be working in the browser before. The error is only thrown from Web, not desktop.

We could programatically deselect the image right before inserting (n.b., works in desktop, though is not needed there anyway), but using setSelectedShapes([]) does nothing in browser (see #3102, #3083).

Added the information about deselecting again in this issue since it provides context to why throwing an error on setSelectedDataAsync is not useful. Also highlighting that there are no errors logged when setting the selection to nothing fails, and it seems to cause the PowerPoint.run promise to never resolve.

Useful logs

Thank you for taking the time to report an issue. Our triage team will respond to you in less than 72 hours. Normally, response time is <10 hours Monday through Friday. We do not triage on weekends.

Searion commented 1 year ago

Thanks for reporting this issue.
It has been put on our backlog<Bug#8369126> for internal track. We will keep track of this issue and let you know if there are any updates.

nikhilatsap commented 9 months ago

I am facing the same issue for same kind of problem. We are also developing an add-in with similar functionality and my issue is not with replacing the image, which I can achieve using getSelectedShapes and then replacing it, but adding a second image with tags , while an image with custom tags is already selected on some slide. So if an image is already selected in the slide, and Then If I try to add an image to the slide using setSelectedDataAsync it throws error code 2001, I am hoping this would be resolved with this same bug?

keepeek-rd commented 1 month ago

if it helps, i implemented this alternative code to reset the current slide selection:

const resetPowerPointSlideItemsSelection = async (): Promise<void> => {
  const currentSlideId: number = await new Promise<number>((resolve, reject) => {
    Office.context.document.getSelectedDataAsync<PowerPoint.Presentation>(
      Office.CoercionType.SlideRange,
      function (asyncResult) {
        if (asyncResult.status == Office.AsyncResultStatus.Failed) {
          reject(asyncResult);
        } else {
          resolve(asyncResult.value.slides[0].id);
        }
      }
    );
  });
  return new Promise<void>((resolve, reject) => {
    Office.context.document.goToByIdAsync(currentSlideId, Office.GoToType.Slide, function (asyncResult) {
      if (asyncResult.status == Office.AsyncResultStatus.Failed) {
        reject(asyncResult);
      } else {
        resolve();
      }
    });
  });
};