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

refactor: event editor color picker #1400

Merged

Conversation

Shobhit-Nagpal
Copy link
Contributor

closes #1397

Attaching a video for the PR

ontime-pr-2024-12-21_01.47.26.mp4

Changes

  • Made changes to <PopoverPicker> to take in icon and hasInput as optional props + added <HexColorInput> from react-colorful if hasInput is true
  • Added <EventColorPicker> as a component which uses <PopoverPicker> passing in icon and hasInput props
  • Added a debounceWithValue function under utils/debounce
  • Debouncing changes on color picker, current wait time is set to 250ms (let me know if you'd like me to change this)
  • Added styling on <HexColorInput> to look consistent with other input fields present

Hope this is what you had in mind, do let me know if there's any changes required :))

P.S: Super fun code base, super clean. Loved working on this

Copy link
Contributor

coderabbitai bot commented Dec 20, 2024

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Owner

@cpvalente cpvalente left a comment

Choose a reason for hiding this comment

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

Thank you very much for this. The code is clean and it follows project conventions. I also really appreciate the demo video. This is a great first contribution

I have left some small cleanup notes for you to consider.
I also noticed that the changes did not run through linter and you likely are missing a setup on your side

Asides from the cleanup suggestions, we need to revisit the debouncing logic to make sure we can provide good performance and UX to the feature

Dec-21-2024 10-30-06

Feel free to reach out if I can help

apps/client/src/common/utils/debounce.ts Outdated Show resolved Hide resolved
Comment on lines 30 to 34
<div className={style.inline}>
<PopoverPicker color={value} onChange={debouncedChange} icon={icon} hasInput={true} />
<input type='hidden' name={name} value={value} />
</div>
);
Copy link
Owner

Choose a reason for hiding this comment

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

question: I dont think we need the inline styles or the wrapper element at all since the input is not visible..
As far as I can test, a fragment here can suffice

      <PopoverPicker color={value} onChange={debouncedChange} icon={icon} hasInput={true} />

Comment on lines 16 to 18
const debouncedChange = debounceWithValue((value: string) => {
setColour(value);
}, 250);
Copy link
Owner

Choose a reason for hiding this comment

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

we should keep in mind that changing the element colour invalidates the entire array list of elements
unfortunately this amount of debouncing is doesnt seem sufficient to
a) prevent issues with re-renders
b) provide a good user experience with the picker

I wonder if it would be possible for us to only submit on MouseUp event

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe adding simple "OK/Cancel" buttons would be good?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

let me know which would be better. i've finished up on most of the tasks, was going to take this up next.

to use the onMouseUp event, will have to add the handler in the <PopoverPicker> component which is using onChange currently. If that's the behavior you're looking at other places, I'll add that

Copy link
Owner

Choose a reason for hiding this comment

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

I believe both options have its merits, MouseUp may be simpler while the buttons may be more explicit.

In my opinion, the UX of both options is equivalent and likely a matter of preference. For this reason I would favour the option that leads to the most elegant code

I will leave it up to you to make a proposal on this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe directly using the color picker without adding buttons specifically for the <EventEditor> seems like a good user experience. As for debouncing, I figured out why the issue was happening.

I've changed the function signature to this and it works well with parameters now:

export function debounce(callback: (...args) => void, wait: number) {
  let timeout: NodeJS.Timeout | null;
  return (...args) => {
    if (timeout) {
      return;
    }
    timeout = setTimeout(() => {
      timeout = null;
      callback(...args);
    }, wait);
  };
}

The issue that's occurring when we try to debounce is that the function is more of a throttle than a debounce. Happens to be a small but significant difference.

Upon changing the condition where if there's a timeout then return, to clearing the timeout, it's working well with the color picker.

export function debounce(callback: (...args) => void, wait: number) {
  let timeout: NodeJS.Timeout | null;
  return (...args) => {
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      timeout = null;
      callback(...args);
    }, wait);
  };
}

Would you like me to change the debounce function to this and allow it to apply everywhere it's currently being used or add a debounce and throttle function separately and make the respective changes where debounce was previously being used to throttle?

Copy link
Owner

Choose a reason for hiding this comment

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

Thank you for bringing this up.
I honestly always need to google throttle vs debounce as I tend to not be sure of the nuances

Throttle: Ensures that a function is called at most once in a specified time period.
Debounce: Ensures that a function is called only after a specified time period has passed since the last call.

In which case, you are correct that the existing function is a throttle with leading edge.

As for your question:

Would you like me to change the debounce function to this and allow it to apply everywhere it's currently being used or add a debounce and throttle function separately and make the respective changes where debounce was previously being used to throttle?

I believe the second option is the correct. We do not want to make changes to the existing features.
So we would prefer renaming the existing debounce to throttle and add a new debounce function as you suggest

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I honestly always need to google throttle vs debounce as I tend to not be sure of the nuances

Haha, same. Had to google it myself to be sure.

I'll get on this task. To give an update:

  • The <EventColorPicker> component is now <SwatchPicker> and I've put it under the respective directory too as specified.
  • I've added the <SwatchPicker> component within the <SwatchSelect> component and it seems to have solved the wrapping issue too.
  • Added and removed styles accordingly as mentioned
  • Fixed the linting issue as well, I believe. Had to run a pnpm lint on my end. Thanks for letting me know about that.

Once I'm done with this, should send in another push soon

@Shobhit-Nagpal
Copy link
Contributor Author

sure thing, i'll start off working on these changes and then ask for any opinion or guidance on the individual changes

@Shobhit-Nagpal
Copy link
Contributor Author

Hey, added the changed mentioned.

  • <EventColorPicker> is now <SwatchPicker> and is located under components/input/color-input.
  • Shifted <SwatchPicker> within <SwatchSelect>
  • Added a throttle function and made changes accordingly to where it was being used previously.
  • Fixed debouncing on <SwatchPicker>
  • Styling changes
  • Updated the branch with latest changes

It was also mentioned that the icon class may not be required. I removed and tried to simply work with the swatch class itself but it wasn't giving the desired ui. The icon class mainly overrides some styles from the swatch class now.

Let me know if any further changes are required

@Shobhit-Nagpal
Copy link
Contributor Author

Attaching a video for the recent changes:

ontime-pr-2-2024-12-23_17.44.07.mp4

Copy link
Owner

@cpvalente cpvalente left a comment

Choose a reason for hiding this comment

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

Thank you for your work, really great.
I apologise for being slow in providing feedback at this time of year

I have left some small nitpicks for your consideration, only requested change is to fix the typing for the throttle and debounce functions.
It also seems that you have merge conflicts with master which we would need to resolve

@Shobhit-Nagpal
Copy link
Contributor Author

Thank you for your work, really great.
I apologise for being slow in providing feedback at this time of year

Not at all. I'm extremely grateful for the review and suggestions you're giving. I've learnt quite a few things through these changes, super grateful :)

I'll add in the recent changes suggested, fix the conflicts and send in another push

@Shobhit-Nagpal
Copy link
Contributor Author

Do let me know if there's any change to be done on the function signature. I believe with TypeScript that's the way to do it, in case there's a better way, would love to know about it

Copy link
Owner

@cpvalente cpvalente left a comment

Choose a reason for hiding this comment

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

Thank you, we need to resolve the uncaught error introduced with migrating the code to the color library
Otherwise the code looks good and I look forwards to merging it


const classes = cx([style.swatch, isSelected ? style.selected : null, style.selectable]);

const iconColor = Color(color).isLight() && isSelected ? '#000000' : '#FFFFFF';
Copy link
Owner

Choose a reason for hiding this comment

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

The tests are correctly catching an issue where the code will crash if the colour is not a valid colour string (ie: a empty string)

This process needs to be caught, similarly to what we do in thegetAccessibleColour I linked earlier

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, interesting. thanks, added this on the recent push

Copy link
Owner

@cpvalente cpvalente left a comment

Choose a reason for hiding this comment

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

Nice 🎉
Great work, lets fix the merge conflicts and we are ready to go

@Shobhit-Nagpal
Copy link
Contributor Author

sure thing. let me know how you'd like to proceed with that

@cpvalente cpvalente merged commit 6bbf8ce into cpvalente:master Dec 27, 2024
2 checks passed
@cpvalente
Copy link
Owner

cpvalente commented Dec 27, 2024

sure thing. let me know how you'd like to proceed with that

Merged! I appreciate your contribution, thank you again.
Would you be interested in picking up a follow up task?

We want to implement your changes to the colour picker in the ViewParamEditor
https://github.com/cpvalente/ontime/blob/master/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx

So your SwatchPicker will be used instead of the current InlineColourPicker
https://github.com/cpvalente/ontime/blob/master/apps/client/src/common/components/view-params-editor/InlineColourPicker.tsx

@Shobhit-Nagpal
Copy link
Contributor Author

Shobhit-Nagpal commented Dec 27, 2024

Merged! I appreciate your contribution, thank you again.

I loved working on this. Huge thanks to you for the reviews and suggestions, they really helped.

Would you be interested in picking up a follow up task?

Yes, of course! I'll raise another PR for this. Anything else other than this?

@cpvalente
Copy link
Owner

else other than this

We have a large-ish backlog with lots of react task, but you would need to build some more familiarity with the code base to be able to be productive
But I am sure we can find some tasks going further, you are also welcome to reach out with some suggestions based on your interests

@Shobhit-Nagpal
Copy link
Contributor Author

But I am sure we can find some tasks going further, you are also welcome to reach out with some suggestions based on your interests

Sure thing.

Regarding the change on <InlineColourPicker>, I noticed that we're passing in only the name and value as props and have the state for colour change in the component itself and I believe it's not saving changes currently. The <SwatchPicker> takes in a onChange as one of the props.

If we're thinking to replace the <InlineColourPicker> with the <SwatchPicker>, we'd probably need to:

  1. Pass in (and maybe introduce) the onChange callback from the <ParamInput> component
  2. Replace the <PopoverPicker> with the <SwatchPicker> within <InlineColourPicker>

Let me know which approach you'd like to take. Apologies for any delay on my end

@cpvalente
Copy link
Owner

Regarding the change on <InlineColourPicker>, I noticed that we're passing in only the name and value as props and have the state for colour change in the component itself and I believe it's not saving changes currently. The <SwatchPicker> takes in a onChange as one of the props.

If we're thinking to replace the <InlineColourPicker> with the <SwatchPicker>, we'd probably need to:

  1. Pass in (and maybe introduce) the onChange callback from the <ParamInput> component
  2. Replace the <PopoverPicker> with the <SwatchPicker> within <InlineColourPicker>

Let me know which approach you'd like to take. Apologies for any delay on my end

The params editor is based on an HTML form element, on submit we convert the values into parameters and push it to the window location.
All the elements are uncontrolled and the layout is built from data by a factory function

I believe these design elements are good and worth maintaining.
A good solution here would avoid introducing state and callbacks into the factory

With this in mind, I believe that replacing the PopoverPicker with the SwatchPicker is the most elegant solution since InlineColourPicker already solves the issue of using a controlled component in an uncontrolled form

@Shobhit-Nagpal
Copy link
Contributor Author

All the elements are uncontrolled and the layout is built from data by a factory function

Super interesting. I'm learning a lot honestly. My bad in case I've missed out something and thank you for telling me when I am missing out anything. Really grateful.

With this in mind, I believe that replacing the PopoverPicker with the SwatchPicker is the most elegant solution since InlineColourPicker already solves the issue of using a controlled component in an uncontrolled form

Made this change and raised a PR under #1412

alex-Arc pushed a commit that referenced this pull request Jan 2, 2025
* refactor: event editor color picker

* refactor: throttle and debounce function signature
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.

create colour picker for event colour
3 participants