Skip to content

Conversation

AkashDhiman
Copy link
Member

@AkashDhiman AkashDhiman commented Apr 3, 2021

Previous functionality:

  • On pressing attach file from compose menu, attachment was directly
    dispatched to outbox, they should go through compose box so that a user
    may add more text if desired.
  • Users could only attach single files using the attach file option.

This commit modifies handleFilesPicker (previously handleFilePicker) to
support picking multiple files. After picking multiple files the
corresponding data is passed to insertAttachment method of ComposeBox
where the content is uploaded one by one and the ComposeBox is updated
accordingly.
Note that dispatch to uploadFile no longer takes place now.

Fixes-part-of: #4540
Fixes-part-of: #2366

Demonstration of Changes:

Simple Attachment:

attachment-simple

Attachment in between text:

attachment-between-text

Attachment without Internet:

attachment-without-internet

Caveats / Not addressed in this PR:

  • if a user selects the file to attach (from file selection screen) by pressing file preview instead of the footer (section of the file that contains name, size time etc) the file is immediately sent to zulip. User needs to tap and hold the file preview in order to select multiple files, or alternatively they can just tap the footer as seen in the first gif of this comment (see above).
    simple press
    simple-press
    press and hold
    press-and-hold
  • The error text in toast when upload fails, is not translated.
  • Loader that depicts that file is being uploaded, is not implemented.

@gnprice
Copy link
Member

gnprice commented Apr 8, 2021

Thanks @AkashDhiman! This is great -- these are improvements we've wanted for a while, and I quite appreciate the screencaps and the clear description of caveats.

A couple of high-level comments:

First, I think we probably do want to keep the "image" flow, even though it's possible to do all the same things via the "file"/"attachment" flow. Two reasons:

  • Having an "attach image" option is standard in chat or messaging apps, so that it's something people expect to be there. It's probably a lot more commonly used than "attach file" -- I suspect many users have often used "attach image" in various other apps that have never used "attach file". So if we don't have an icon for it, I expect there will be people that try Zulip and try to send an image, and end up thinking that we don't support it.
  • Even though it's possible to get to your photos from the "attach file" flow, it's more cumbersome -- you start from a list that's oriented toward files (in your screencaps, it starts in the Downloads folder; when I try it on my phone, it shows downloads as well as photos I've taken, but the thumbnails are tiny and most of the space goes to the filenames), and it takes some navigation to get to a good gallery view.

Is it possible to get multi-selection in the image picker, too?

If not, I'll still be very glad to get the part of this PR merged that makes us stop immediately sending the message (and instead inserts a link in the edit box), and then we can figure out what to do about the image picker after that.

Second, the main commit here is really doing two mostly independent things:

  • It switches from immediately uploading the file and sending a message with it, to uploading the file and putting a link in the edit box.
  • It switches from picking just one file, to picking several files.

Those would best be done as two separate commits: first switch to putting a link in the edit box instead of immediately sending the message, and second switch from one file to several files. I think the second of those commits will be fairly straightforward: a couple of small changes in ComposeMenu, and adding the loop in insertAttachment.

I have some smaller code-level comments too, which I'll make as a separate review.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, detailed comments below. Thanks again!

Comment on lines 239 to 240
const response = await api.uploadFile(auth, attachments[i].uri, fileName);
[beg, end] = this.replaceMessageTextAtRange(`[${fileName}](${response.uri})\n\n`, beg, end);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some time may pass while we're at this await, and the user might make some edits in the input box. It looks like if they add or remove text before the start of what we inserted, the effect will be that we replace the wrong range of text, because beg and end no longer point to where it is. That'll likely leave the markup somewhat garbled, and it may also overwrite what they've typed.

I think with the native APIs it would be possible to attach something like a "span" to the relevant chunk of text, so that we can refer back to it even after the user has made edits that change its offset. But it looks like RN doesn't expose that functionality.

An alternative solution would be to look for the placeholder string we inserted -- i.e. [Uploading ${fileName}...]() -- and just replace that at the (first) place we find it. Hmm, and if we don't find it (because e.g. the user edited in the middle of it), then I guess insert it at the end. That seems like it'd be pretty robust.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this seems much better, I am doing the find and replace implementation you suggested, I also checked out what RN provides just in case and they don't expose the "span" functionality just as you mentioned, I believe these were the right documentation to look at: https://reactnative.dev/docs/textinput

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I think that's right!

Comment on lines 235 to 240
for (let i = 0; i < attachments.length; i++) {
const fileName = attachments[i].name ?? `attachment-${i + 1}`;
[beg, end] = this.replaceMessageTextAtRange(`[Uploading ${fileName}...]()\n`, end, end);
try {
const response = await api.uploadFile(auth, attachments[i].uri, fileName);
[beg, end] = this.replaceMessageTextAtRange(`[${fileName}](${response.uri})\n\n`, beg, end);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the overall sequence of what this code does, if there are several files to upload/attach, is going to be:

  • We insert the first placeholder, and start uploading.
  • Eventually the upload finishes. We edit that placeholder, insert the next one, and start uploading.
  • Etc., until we edit the final placeholder.

And I guess that agrees with what I see in your helpful screencaps.

That seems like not quite the ideal sequence -- having the placeholders come in one by one may feel kind of glitchy.

I think a better sequence would be:

  • We insert all the placeholders immediately. We start uploading the first file.
  • Eventually the upload finishes. We edit that placeholder, and start uploading the next file.
  • Etc., until we edit the final placeholder.

(I think this code already makes exactly the right choice in that it only attempts one upload at a time. Uploads are probably often big files, and we may often be on a slow network connection -- no sense hammering it with a lot of uploads at once.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now I am doing it as you suggested (with a simple addition on failure):

  • inserting all placeholders immediately.
  • Search placeholders one by one and replace them with linkText.
  • If any search fails, append the text at the very end of the message.
  • If upload itself fails I am now replacing the placeholder with [Uploading ${fileName} failed.]().

The last point is important to note since it differs from the web app and the previous implementation. I took this decision based on the fact that replacing it with nothing won't be an ideal choice, since it would be very easy to miss out that some uploads have failed (even with a toast message). a permanent replacement with [Uploading ${fileName} failed.](). would be able to indicate a user (or the recipients) that there was more to send but it failed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That reasoning makes sense to me!

If the webapp's behavior is that when the upload fails, you just have the [Uploading ${fileName}...]() placeholder stick around indefinitely, then I think this [Uploading ${fileName} failed.]() behavior would be an improvement there, too. Perhaps mention it in #frontend on chat.zulip.org?

[beg, end] = this.replaceMessageTextAtRange(`[${fileName}](${response.uri})\n\n`, beg, end);
} catch (e) {
[beg, end] = this.replaceMessageTextAtRange('', beg, end);
showToast(_(`Uploading ${fileName} unsuccessful`));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want the string being translated to be a constant string, with a placeholder in it for the filename. See docs/howto/translations.md, and search for "interpolating".

We'll also want to add the string to static/translations/messages_en.json; from there after the change is merged we'll sync it over to Transifex so that contributors there can translate it. See the next few paragraphs in the same doc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a concern regarding this, docs/howto/translations.md says -

Translators will translate the constant string 'Hello, {name}', including the placeholder.

does that mean we also converts the filename variable to another language and if so shouldn't that remain preserved since it indicates the name of the file in user's device?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good question -- that explanation was ambiguous. Does this answer the question?
be39f56

let [beg, end] = [undefined, this.state.selection.start];
for (let i = 0; i < attachments.length; i++) {
const fileName = attachments[i].name ?? `attachment-${i + 1}`;
[beg, end] = this.replaceMessageTextAtRange(`[Uploading ${fileName}...]()\n`, end, end);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll also want this to be translated. See previous comment for link to our docs on how.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same concern as above

@AkashDhiman
Copy link
Member Author

AkashDhiman commented Apr 28, 2021

Sorry for the delay, @gnprice this PR is ready for another review

Reply to your comments:

Is it possible to get multi-selection in the image picker, too?

Unfortunately react-native-image-picker doesn't provide this functionality (here is a comment by its maintainer regarding this).

The way to move forward with this then would be to use another library like react-native-image-crop-picker.

For now I have dropped the commit that removed image-picker functionality in android as suggested, but only added the functionality to send attachment to compose box as well as multiple attachment to the attach files option, i.e. image attachment currently works in this PR just like in master.
I didn't want to update image flow since changing to react-native-image-crop-picker may make those additions useless and given that we have no way to provide multiple attachments using react-native-image-picker we may have to move to some other library eventually.

react-native-image-crop-picker, besides allowing us to pick multiple images also allows us to crop the selected images before sending. This seems like a nice addition to have.
Another benefit I believe we will have is that this module is written in typescript so perhaps that can also be helpful? given that we do not have a valid libdef configuration for react-native-image-picker. (blocker is explained in flow-typed/npm/react-native-image-picker_vx.x.x.js).

Since shifting modules can take time with cleanups etc, there are 2 workarounds for the time being -

  • I can modify handleImagePickerResponse to work with inserting attachments the way I did with documents. disadvantage would be that we could only attach 1 image at a time, and code could be a bit ugly since there is no libdef.
  • I can use DocumentPicker to handle images attachment instead of react-native-image-picker, It can be configured to only ask for images (in this mode it only displays file type of images), advantage would be that we can pick multiple images. Disadvantage would be that image previews would be as large as the screen captures above (personally react-native-image-picker previews aren't that big either so I prefer this approach).

Or perhaps we can just ignore and settle it after migrating to another module.

There are some translation concerns -

let placeholders = '';
for (let i = 0; i < attachments.length; i++) {
const fileName = attachments[i].name ?? `attachment-${i + 1}`;
placeholders += `${_('[Uploading {fileName}...]()', { fileName })}\n\n`;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this counts as a string concatenation which is denied in /docs/howto/translations.md

Never try to concatenate translated strings together, or do other string manipulation on them.

is this reasonable code? I kept it here since it only involves concatenation with newlines, which shouldn't create a problem according to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for asking. Yeah, this is fine as far as that's concerned -- what that's really meant to be about is attempting to put together a sentence with translated text, because the structure of the sentence will be different in different languages. I guess I should try to clarify that bit in the doc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gnprice
Copy link
Member

gnprice commented Apr 29, 2021

Unfortunately react-native-image-picker doesn't provide this functionality (here is a comment by its maintainer regarding this).

Cool, good to know.

Though actually -- that comment is several years old, and I believe the set of maintainers more or less turned over last year, along with a new major version. So the comment doesn't necessarily represent the current maintainers' view. But if it doesn't have the feature, then that's that.

The way to move forward with this then would be to use another library like react-native-image-crop-picker.

Yeah, that may be something to experiment with, then. Definitely out of scope for this PR, though -- #4540 will already be great to fix.

let placeholders = '';
for (let i = 0; i < attachments.length; i++) {
const fileName = attachments[i].name ?? `attachment-${i + 1}`;
placeholders += `${_('[Uploading {fileName}...]()', { fileName })}\n\n`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for asking. Yeah, this is fine as far as that's concerned -- what that's really meant to be about is attempting to put together a sentence with translated text, because the structure of the sentence will be different in different languages. I guess I should try to clarify that bit in the doc.


for (let i = 0; i < attachments.length; i++) {
const fileName = attachments[i].name ?? `attachment ${i + 1}`;
const placeholder = _('[Uploading {fileName}...]()', { fileName });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of computing this a second time, how about having an array placeholders, so that here we can just say placeholders[i]?

Then we can still concatenate them for the insertMessageTextAtCursorPosition call, say with placeholders.join('\n\n').

The main advantage of computing them just once is that when we're doing it twice, it makes me worry a bit about whether they'll definitely come out the same both times. I'm pretty sure they always do with the code in this PR, but it's the sort of thing where it'd be very easy for them for a future change to the code to accidentally get them out of sync -- so, better to write the code so that structurally it makes sure they're the same.

Copy link
Member Author

@AkashDhiman AkashDhiman Apr 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made array of placeholders as well as fileNames. I also accidentally got the "replacement fileName" (attachment ${i+1}) out of sync in this very commit 😅 (you can see it in the code highlighted here) so this makes it a lot more relevant.
btw I fixed that as well and added a corresponding translation string for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, indeed! And I didn't notice that either 😁 So that's a nice live demonstration of the point.

Comment on lines 240 to 268
try {
const response = await api.uploadFile(auth, attachments[i].uri, fileName);
const linkText = `[${fileName}](${response.uri})`;
if (this.state.message.indexOf(placeholder) !== -1) {
this.setMessageInputValue(this.state.message.replace(placeholder, linkText));
} else {
this.setMessageInputValue(`${this.state.message} ${linkText}`);
}
} catch (e) {
showToast(_('Uploading {fileName} unsuccessful.', { fileName }));
this.setMessageInputValue(
this.state.message.replace(
placeholder,
_('[Uploading {fileName} failed.]()', { fileName }),
),
);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this try/catch be tightly scoped around just the await api.uploadFile line. That way the error handling in the catch block gets to be focused on a very specific way things can fail, which is the API call.

The fundamental difference between an exception from the API call and an exception from any of the rest of this code is that an exception from any of the rest would represent a bug in our code. But with an API call it's part of its interface that it might throw an exception for reasons that aren't a bug in anything, just the way the network is at that moment. Generally when throwing an exception is a normal part of the interface of something, we want to handle it explicitly right there, but for exceptions that would represent a bug we want to let them propagate up to very general handlers at the outer layers of the app.

One way in particular that having this larger try block would be bad, if a situation ever arises where it makes a difference, is that it would mean we had a bug somewhere in the code that's trying to replace the placeholder with the link text -- and we'd be suppressing the error here, so that we didn't get a report of it in Sentry to help us find and fix it.

Copy link
Member Author

@AkashDhiman AkashDhiman Apr 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I was wondering why this specific code pattern --

try {
  mayThrowError()
} catch (e) {
  // handle error
  return;
}
// handle success

-- was present at some places, instead of just handling success inside the try block after mayThrowError. Infact I changed this in e6bcb0e (in #4514) thinking it was not necessary, which you also addressed in #4514 (comment). This makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, exactly.

I've also now gone and expanded this to an entry in our style guide: d99ad3a

Comment on lines 235 to 240
for (let i = 0; i < attachments.length; i++) {
const fileName = attachments[i].name ?? `attachment-${i + 1}`;
[beg, end] = this.replaceMessageTextAtRange(`[Uploading ${fileName}...]()\n`, end, end);
try {
const response = await api.uploadFile(auth, attachments[i].uri, fileName);
[beg, end] = this.replaceMessageTextAtRange(`[${fileName}](${response.uri})\n\n`, beg, end);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That reasoning makes sense to me!

If the webapp's behavior is that when the upload fails, you just have the [Uploading ${fileName}...]() placeholder stick around indefinitely, then I think this [Uploading ${fileName} failed.]() behavior would be an improvement there, too. Perhaps mention it in #frontend on chat.zulip.org?

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @AkashDhiman for the revision -- this looks great! A few comments, above and below.

Let's also have these commits not say "Fixes" yet for those two issues #4540 and #2366, because they don't yet apply to the "add an image" flow, and that's the one that I think most users are interested in most often. We'll say "Fixes" in a later commit that fixes each issue for that flow too.

  • I can use DocumentPicker to handle images attachment instead of react-native-image-picker, It can be configured to only ask for images (in this mode it only displays file type of images), advantage would be that we can pick multiple images. Disadvantage would be that image previews would be as large as the screen captures above (personally react-native-image-picker previews aren't that big either so I prefer this approach).

Yeah, I'm perfectly happy with the size of previews that are in your screen captures above. When I tried it on my phone, I got much smaller thumbnails (like maybe 48px -- the size of our avatars in the message list), in more of a "list of files" view than a "gallery" view. It looks like there's an icon in the upper right that switches back and forth.

The other issue in the UX seen in those screen captures is that it goes straight to "Downloads", which isn't going to be very helpful if what someone's trying to do is to upload a photo they just took, or a screenshot.

If we can have DocumentPicker go directly to a view that's like "Images" instead of "Downloads", then that sounds like a good solution.

@@ -179,10 +180,10 @@ class ComposeMenu extends PureComponent<Props> {
}}
/>
{Platform.OS === 'android' && (
<IconFile
<IconAttach
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, in the commit message:

ComposeMenu: Refactor `IconFile` to `IconAttach`.

let's use a different verb instead of "refactor" -- like "Replace IconFile with IconAttach."

(Or perhaps better yet: "Use IconAttach instead of IconFile." I like having the summary line say first what the new code is doing, in preference to what the old code did -- usually that's the more important thing to know when going back through the history.)

The reason is that "refactor" to me means a change that's mainly about how the code is, rather than how it behaves to the user -- the paradigmatic case is a change that gives the code some very different structure, while having no visible effect at all on how the code behaves for the user. Whereas the point of this change is 100% about what the user sees.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

let placeholders = '';
for (let i = 0; i < attachments.length; i++) {
const fileName = attachments[i].name ?? `attachment-${i + 1}`;
placeholders += `${_('[Uploading {fileName}...]()', { fileName })}\n\n`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but a different translation-related remark:

Let's have the translated string here be just Uploading {fileName}..., and interpolate the result inside the [${…}]() markup syntax.

That way what the translators have to deal with is simpler -- no extra markup on the outside, which we'd be counting on them to keep verbatim and we don't have a great way to make that clear to them.

And on the other hand that's perfectly fine from the perspective of the question in the https://github.com/zulip/zulip-mobile/pull/4590/files#r622602182 thread, because the extra stuff we're adding is purely syntax from the markup language, not anything that's part of English or the user's natural language.

(Conversely, we do want to keep the ... inside the translated string, because that's punctuation and it might be expressed differently in another language.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, did these changes.

@AkashDhiman
Copy link
Member Author

AkashDhiman commented Apr 29, 2021

Let's also have these commits not say "Fixes" yet for those two issues #4540 and #2366,

Changed to "Related"

If we can have DocumentPicker go directly to a view that's like "Images" instead of "Downloads", then that sounds like a good solution.

I think it goes to the last opened window, which in my screen capture was Downloads. At the very first attempt it opens at recent screen which shows me "recent images on phone" for images, this seems reasonable I think.

One concern I forgot to mention before suggesting to use DocumentPicker for images is that it doesn't work for iOS at this time see #4586, I am not sure what the reason for the error is. The docs for react-native-document-picker says that we also have to do a pod install inside ios directory after adding the module, which could be a reason but I have no way to debug this further (no mac) and adding DocumentPicker for images might break attachments for iOS even more.
So how should I proceed with this?

@gnprice besides this I have addressed other changes that you requested and you may check them out.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @AkashDhiman for the revision! Comments below -- just one substantive issue and a few nits. Then this will be ready to merge.

Comment on lines 254 to 257

this.setState(({ numUploading }) => ({
numUploading: numUploading - 1,
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new return in that catch block above causes me to notice a pitfall here: if anything in the function above this point exits the function early, this numUploading decrement won't run and we'll keep thinking there's an ongoing upload forever (until the user navigates out of the narrow so that this component gets discarded.)

In particular that will happen if the new return fires, but also in the previous revision it could happen if for some reason something inside that catch block (like the this.setMessageInputValue call, for example) were to throw an exception. (At the end of the branch, there's no longer the return but there's even more code in the function, so there are more opportunities for something to throw an exception.)

The way to fix this, in a way where it's easy to tell by looking at the code that it's definitely taken care of, is to put this decrement in a finally block. So the function might look like this:

  insertAttachment = async (attachments: DocumentPickerResponse[]) => {
    this.setState(({ numUploading }) => ({
      numUploading: numUploading + 1,
    }));
    try {
      // …
    } finally {
      this.setState(({ numUploading }) => ({
        numUploading: numUploading - 1,
      }));
    }
  };

It's a bit annoying because it causes the whole rest of the function to be indented -- really the ideal pattern here would be RAII, like in C++ or Rust. But this is probably the best approximation to that that we have in JS.

@gnprice
Copy link
Member

gnprice commented May 20, 2021

(Here we're discussing followup changes for after this PR.)

If we can have DocumentPicker go directly to a view that's like "Images" instead of "Downloads", then that sounds like a good solution.

I think it goes to the last opened window, which in my screen capture was Downloads. At the very first attempt it opens at recent screen which shows me "recent images on phone" for images, this seems reasonable I think.

OK, I guess some more playing with this is in order, then (and probably a chat thread in #mobile is the right venue.) If it opens the first time to something with a useful gallery view of photos, and stays that way unless you've chosen something else, then that's good.

One concern I forgot to mention before suggesting to use DocumentPicker for images is that it doesn't work for iOS at this time see #4586

Hmm, indeed, thanks for filing that. I've replied on that thread.

gnprice added a commit that referenced this pull request May 20, 2021
Expanded from a couple of PR comments I made recently:

  #4514 (comment)
  #4590 (comment)
@AkashDhiman AkashDhiman force-pushed the attachment branch 3 times, most recently from 284595b to 537cde3 Compare June 17, 2021 14:13
@AkashDhiman
Copy link
Member Author

@gnprice made the changes you requested.
I am currently leaving the image and camera part for another PR since that would require some extra work which is unrelated (from an implementation P.O.V.) to these changes.

A good thing is that we no longer would need to replace react-native-image-picker since they added support for multiple images last month. So I will probably update the dependency and see how things go and make a separate PR for that.

AkashDhiman and others added 5 commits June 30, 2021 17:33
The functionality provided by this option is to attach file and hence
IconAttach (paperclip icon) is a much better placeholder for it.
Previous syntax corresponded to returning a Promise of array of length
1, where the element has a type of `DocumentPickerResponse`.

Now it is changed to - a Promise of array of `DocumentPickerResponse`.

This is the desired return type for `pickMultiple`.
On pressing attach file from compose menu, attachment was directly
dispatched to outbox, they should go through compose box so that
a user may add more text if desired.

This commit creates a new function `insertAttachment` that is
utilized in `ComposeMenu`. It performs the task of uploading the
attachment as well as updating the `ComposeBox` message accordingly.

Fixes part of zulip#4540 because behaviour of image picker option is
unchanged.

Fixes-part-of: zulip#4540
This makes it straightforward to look at this code and see that the
increment (with `numUploading + 1`) is always paired with a decrement
(with `numUploading - 1`), so that there isn't a case where it gets
stuck at a higher value due to an exception somewhere.
Previously we could only attach single file using the attach file option
in ComposeMenu. This code updates `handleFilesPicker` (previously
`handleFilePicker`) to pick multiple files, its response is an array of
type `DocumentPickerResponse` which is passed to `insertAttachment` to
upload and insert data to `ComposeBox`.

`insertAttachment` is modified to handle this array. We first insert the
`[Uploading ${filename}...]` placeholder for all attachments, then we
upload files one by one and keep replacing their placeholder with
`linkText` on success or `[Uploading ${fileName} failed.]` on failure.

Fixes part of zulip#2366 because behaviour of image picker option is
unchanged.

Fixes-part-of: zulip#2366
@gnprice gnprice merged commit 4d888e5 into zulip:master Jul 1, 2021
@gnprice
Copy link
Member

gnprice commented Jul 1, 2021

Thanks @AkashDhiman for the revision!

This all looks good, except for the try/finally for decrementing numUploading (from #4590 (comment) ). The idea I had in mind there was to cover the entire rest of the function after the point where we increment it -- that way it's clear that it always runs, regardless of the details of what's in the rest of that code.

I've gone and merged the PR, with an extra commit added to make that change. Take a look: e202c27.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants