Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the chat UI to reflect drag-and-drop #12056

Conversation

vladamx
Copy link
Contributor

@vladamx vladamx commented Oct 21, 2022

Details

  1. Use state machine based approach to detect drag and drop over drop zone efficiently. Ignore events on the children.
    Currently, drag creates a lot of events for all children of drop zone therefore re-rendering ReportActionCompose very frequenly which made UI slugish. This PR fixes that as well.

  2. Created UI as per Figma spec with translations.

  3. Made sure there are no regressions in mobile environments.

Fixed Issues

$ #11823

PROPOSAL: #11823 (comment)

Tests / QA

  1. Login to Expensify
  2. Go to existing report or create new one
  3. Find file you want to drag over to the Report
  4. Drag over should be detected(drag overlay shown) in the messages and composer area.
  5. Drop should not be detected if you leave the area by dragging over LHN, Report header or leaving the browser/desktop window
  6. On drop in the drop zone area, it should trigger the modal with a file preview for supported file types.
  7. On drop outside of drop zone it should ignore the drop. Should not open file in new window.
  • Verify that no errors appear in the JS console

PR Review Checklist

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

PR Reviewer Checklist

The reviewer will copy/paste it into a new comment and complete it after the author checklist is completed

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots

Web

Web-DragNDrop.mp4

Mobile Web - Chrome

Android-Chrome-DragNDrop.mov

Mobile Web - Safari

Safari-DragNDrop.mov

Desktop

Desktop-DragNDrop.mov

iOS

IOS-DragNDrop.mov

Android

Android-DragNDrop.mov

@@ -48,4 +48,5 @@ export default {
placeholderText: colors.gray3,
heroCard: colors.blue,
uploadPreviewActivityIndicator: colors.gray1,
overlayBackgroundColor: colors.translucentWhite,
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to how we are a bit more specific for modalBackground - perhaps we do something similar and call this dragAndDropBackground

Copy link
Contributor

Choose a reason for hiding this comment

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

I just realized I contradicted this in a later review. @shawnborton maybe you can provide some insight on why you prefer the more specific theme color here?

Copy link
Contributor

Choose a reason for hiding this comment

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

overlayBackgroundColor just feels a bit too generic. Is that the overlay we use for modals? It's actually not, so maybe we should call it something more specific.

@@ -18,4 +18,5 @@ export default {
redDisabled: '#fea29a',
yellow: '#fed607',
transparent: 'transparent',
translucentWhite: 'rgba(255,255,255, 0.96)',
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than needing to add a new color value here, can we do something similar to what we've done for the modal background? Basically we just use the standard white color, but then add opacity as a separate style?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually just realized I contradicted this review comment in a later review too. Sorry about the confusion

@vladamx
Copy link
Contributor Author

vladamx commented Oct 24, 2022

@shawnborton Updated

@parasharrajat Can you please take a look at this one? I see that you are not assigned as a reviewer.

@parasharrajat
Copy link
Member

parasharrajat commented Oct 24, 2022

I think you removed the $ from. Please compare it with the latest PR template and fix it.
image

src/components/Composer/index.js Outdated Show resolved Hide resolved
@@ -132,6 +133,15 @@ class Composer extends React.Component {
};
this.dragNDropListener = this.dragNDropListener.bind(this);
this.paste = this.paste.bind(this);
if (document) {
this.dropZone = document.getElementById(CONST.REPORT.DROP_NATIVE_ID);
if (this.dropZone) {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a possibility where dropZone will not be present? If no, please remove this check.

if (this.dropZone) {
this.dropZoneDragHandler = this.dropZoneDragHandler.bind(this);
this.dropZoneDragListener = this.dropZoneDragListener.bind(this);
this.dropZoneDragState = 'dragleave';
Copy link
Member

Choose a reason for hiding this comment

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

Please add a comment. What is this state?

this.props.onDragOver(e, isOriginComposer);
break;
case 'dragenter':
dropZoneDragHandler = (e) => {
Copy link
Member

Choose a reason for hiding this comment

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

No arrow function syntax.

@@ -197,46 +207,73 @@ class Composer extends React.Component {
}

/**
* Handles all types of drag-N-drop events on the composer
* DragEvent
Copy link
Member

Choose a reason for hiding this comment

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

Insufficient comment.

src/pages/home/report/ReportDropUI.js Outdated Show resolved Hide resolved
src/pages/home/report/ReportDropUI.js Outdated Show resolved Hide resolved
src/components/Composer/index.js Outdated Show resolved Hide resolved
src/components/Composer/index.js Outdated Show resolved Hide resolved
src/components/Composer/index.js Outdated Show resolved Hide resolved
@parasharrajat
Copy link
Member

Please use informative commit messages.
image

@vladamx
Copy link
Contributor Author

vladamx commented Oct 25, 2022

Please use informative commit messages. image

👍 This was just the base for the initial proposal

@vladamx
Copy link
Contributor Author

vladamx commented Oct 25, 2022

@parasharrajat

Seems that logic for detecting dragleave on window does not work on firefox unfortunately.

After a little bit more of a research it seems that counter approach is the most reasonable thing that works cross browser.

@parasharrajat
Copy link
Member

Ok, let's do that then.

@vladamx
Copy link
Contributor Author

vladamx commented Oct 27, 2022

@parasharrajat I took a litle bit more time and got the idea to use clientBoundingRect to detect dragleave on dropzone and it works cross browser.

Ready for review.

right: 0,
bottom: 0,
backgroundColor: themeColors.dragAndDropBackground,
opacity: 0.96,
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO this opacity should be defined in the theme file

Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

src/pages/home/report/ReportDropUI.js Outdated Show resolved Hide resolved
src/pages/home/report/ReportDropUI.js Outdated Show resolved Hide resolved
Copy link
Member

@parasharrajat parasharrajat left a comment

Choose a reason for hiding this comment

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

Icon and text should not be opaque.
image

src/styles/styles.js Outdated Show resolved Hide resolved
Comment on lines 14 to 16
onDragOver: DragAndDropPropTypes.onDragOver,

shouldAcceptDrop: PropTypes.func,
Copy link
Member

Choose a reason for hiding this comment

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

Please add comments for these propTypes.

Copy link
Member

Choose a reason for hiding this comment

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

Can we do this please so that I can approve this PR?

@parasharrajat
Copy link
Member

Thanks, will wrap this up in few minutes.

@parasharrajat
Copy link
Member

parasharrajat commented Dec 2, 2022

Screenshots

🔲 iOS / native

Screenshot 2022-11-23 21:43:16

🔲 iOS / Safari

Screenshot 2022-11-23 21:49:40

🔲 MacOS / Desktop

screen-2022-11-23_21.59.42.mp4

🔲 MacOS / Chrome

screen-2022-11-23_21.53.36.mp4
screen-2022-12-03_03.57.28.mp4

🔲 Android / Chrome

Screenshot 2022-11-23 20:38:38

🔲 Android / native

Screenshot 2022-11-23 20:35:04

@shawnborton
Copy link
Contributor

Sorry for catching this so late, but can we use a new icon to match the new branding? You can use this one (feel free to rename it so that we overwrite the old one): document-add.svg.zip

@parasharrajat
Copy link
Member

Good call @shawnborton @vladamx Could you please quickly do that?

Copy link
Member

@parasharrajat parasharrajat left a comment

Choose a reason for hiding this comment

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

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

cc: @roryabraham

🎀 👀 🎀 C+ reviewed

Copy link
Member

@parasharrajat parasharrajat left a comment

Choose a reason for hiding this comment

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

@shawnborton
Copy link
Contributor

Here is a better icon, sorry about that! drag-and-drop.svg.zip

@shawnborton
Copy link
Contributor

Can we just get an updated screen shot/video with the new icon?

@vladamx
Copy link
Contributor Author

vladamx commented Dec 3, 2022

I think we are out of time budget for this PR? cc @roryabraham Can we allocate compensation for redesign changes and regression tests?

@parasharrajat
Copy link
Member

@vladamx Could you update the recordings on the PR with the latest? @roryabraham This is ready for your review.

@vladamx
Copy link
Contributor Author

vladamx commented Dec 6, 2022

@parasharrajat Updated

@parasharrajat
Copy link
Member

cc: @roryabraham

Copy link
Contributor

@roryabraham roryabraham left a comment

Choose a reason for hiding this comment

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

Overall I have some thoughts on how this could be cleaned up, but I think it's good enough and I'd like to get it over the finish line so we can pay this out and it unblocks other related work. I'll likely create a small PR to address my remaining comments.

const defaultProps = {
onDragOver: () => {},
shouldAcceptDrop: (e) => {
if (e.dataTransfer.types) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This block could use a comment, imo it's not super what logic we're implementing here. I think we're trying to ensure that Drag & Drop only appears for files and not other things (like links?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, exactly.

src/pages/home/report/ReportDropUI.js Show resolved Hide resolved
@@ -97,6 +99,8 @@ const oldTheme = {
placeholderText: colors.gray3,
heroCard: colors.blue,
uploadPreviewActivityIndicator: colors.gray1,
dropUIBG: 'rgba(6,27,9,0.92)',
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is correct, but we're not using the old theme for now anyways, so NAB.

@roryabraham roryabraham merged commit 748228f into Expensify:main Dec 7, 2022
@OSBotify
Copy link
Contributor

OSBotify commented Dec 7, 2022

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@marcaaron
Copy link
Contributor

marcaaron commented Dec 8, 2022

cc @francoisl since you are NewDot deployer atm.

Wanted to mention that I spotted what appears to be a pretty bad bug/crash while testing main locally and reported here:

https://expensify.slack.com/archives/C01GTK53T8Q/p1670528976295469

Unfortunately, I can't provide repro steps and haven't reproduced on staging (edit: I see its not on staging yet 😄 ) - but the app was rendered unusable and the only recourse was to clear storage to get it back up and running (sign out did not resolve). I'm not sure if this qualifies as a deploy blocker or what actions we want to take here. I have a slight concern that if something does need addressing and this gets passed QA then we will deploy a bad change to prod.

@OSBotify
Copy link
Contributor

🚀 Deployed to production by @chiragsalian in version: 1.2.38-6 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

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.

6 participants