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

Add confirmation prompt before moving groups to the recycling bin, Refactor MessageBox to allow custom buttons #2376

Conversation

Projects
None yet
3 participants
@kneitinger
Copy link
Contributor

kneitinger commented Oct 9, 2018

UPDATE:

The scope of this PR changed quite a bit since it was open. Originally it was to add a confirmation before moving groups to the recycle bin, which turned into modifying all questions with destructive actions to have buttons that reflect the operation instead of yes/no. To implement that, MessageBox was rewritten to act analogously to how it did when it was QMessageBox::StandardButton-based, but allows for custom buttons.

The manual test cases I used can be seen in this comment

Below is the original PR, other requests can be found in the comments.

Description

Spawn a yes/no QMessage box when "Delete Group" is selected on a group
that is not already in the recycle bin (note: the prompt for deletion
from the recycle bin was already implemented). This follows the same
pattern and language as entry deletion.

Motivation and context

Fixes #2125

How has this been tested?

  • Group "TestGroup" added
  • Right clicked and selected "Delete Group"
    • Prompt Do you really want to move the group "TestGroup" to the recycle bin? is shown
    • 2018-10-08_18-31-34
    • Chose "No", group was not moved to recycle bin.
    • Repeated process and chose "Yes", group was moved to recycle bin
    • Repeated above steps from top of window menu bar -> Groups -> Delete Group, same results
  • Ran existing tests

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • I have read the CONTRIBUTING document. [REQUIRED]
  • My code follows the code style of this project. [REQUIRED]
  • All new and existing tests passed. [REQUIRED]
  • I have compiled and verified my code with -DWITH_ASAN=ON. [REQUIRED]
Add confirmation prompt before moving groups to the recycling bin
Spawn a yes/no QMessage box when "Delete Group" is selected on a group
that is not already in the recycle bin (note: the prompt for deletion
from the recycle bin was already implemented).  This follows the same
pattern and language as entry deletion.

Fixes #2125
@phoerious

This comment has been minimized.

Copy link
Member

phoerious commented Oct 20, 2018

TBH, I am not a big fan of Yes/No boxes. There is almost never a situation where they are good UX design. I vote for changing the buttons to"Delete" and "Cancel".

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Oct 20, 2018

@phoerious I could be into that. I made this consistent with the existing dialogs for moving an entry to the recycling bin, so I would want to change those as well.

@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Oct 24, 2018

I do agree with @phoerious on this one. It makes a lot of sense, especially for quick questions, to have the (complete) answer as the choice in the button. Under Material design rules, you would also make the delete button red, but that is overkill. I think most of our dialogs will go this route, perhaps we should extend the MessageBox class to include these additional choices. See: http://doc.qt.io/qt-5/qmessagebox.html#advanced-usage

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Oct 26, 2018

@phoerious @droidmonkey I went through the src dir and found all of the occurrences of prompts in which choices are present. Some of them match the action description style that we're moving towards, but the majority of them are Yes/No. Happy to go through and fix all of these for this PR, unless you feel that it would make more sense to put them into their own PR:


Occurrences of QMessageBox questions

note: line numbers may be out of date

autotype/AutoType.cpp:

  • 739: long delay; proceed?: Y/N
  • 748: slow keypresses; proceed?: Y/N
  • 759: args repeated very often; proceed?: Y/N

browser/BrowserService.cpp:

  • 203: shared encryption key already exists, overwrite?: Y/N
  • 362: update entry information?: Y/N

gui/DatabaseTabWidget.cpp:

  • 280: is in edit mode, discard changes and close anyway?: Discard/Cancel
  • 297: DB was modified, save changes? Yes/Discard/Cancel
  • 387: Disable safe saves and try to save again?: Y/N
  • 737: Can lock db, since currently editing: Discard(which locks db)/Cancel
  • 750: Locking but DB modified. save before lock?: Save/Discard/Cancel

gui/DatabaseWidget.cpp:

  • 472: delete entry permanently: Y/N
  • 490: move entry to recycle bin: Y/N
  • 623: execute URL if command: Y/N
  • 682: delete group permanently: Y/N
  • 691: move group to recycle bin: Y/N
  • 1248: db file has changed, reload?: Y/N
  • 1271: db file change and you have unsaved changes, merge?: Y/N
  • 1489: Empty recycle bin?: Y/N

gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp:

  • 250: very high number of rounds warning argon2: "Understood, keep number"/Cancel
  • 267: very high number of rounds warning aes-kdf: "Understood, keep number"/Cancel

gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp:

  • 178: continue without a password warning: Y/Cancel

gui/EditWidgetIcons.cpp:

  • 441: confirm deletion of icon being used by other entries: Y/N

gui/EditWidgetProperties.cpp:

  • 69: Delete plugin data?: Y/Cancel

gui/entry/EditEntryWidget.cpp:

  • 809: apply generated pw to entry?: Y/N
  • 932: entry has unsaved changes: Cancel/Save/Discard
  • 1039: confirm removal of attribute: Y/N

gui/entry/EntryAttachmentsWidget.cpp:

  • 166: confirm removal of attachment: Y/N
  • 213: confirm overwrite of existing file with attachment: Y/N/Cancel
@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Oct 26, 2018

And to touch on @droidmonkey's last comment:

I'm not sure I have the eye to make delete-centric buttons red without making them look like trash 🙂

It seems there are some patterns visible in occurrences listed in my last comment. I agree it would be nice to add some MessageBox functions to cover some of the common cases.

@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Oct 26, 2018

Which ones do you recommend changing? Most of them seem fine to me (like your comment suggests). The big one for me is on destructive changes there should be explicit actions (Delete/Cancel/Discard/Save) instead of Yes/No question.

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Oct 28, 2018

I misinterpreted the request to change all yes/no questions to be explicit actions. Here are the questions that I feel make destructive changes, and what I think they should be changed to. Let me know if any of them need adjustment.

Proposed modifications

autotype/AutoType.cpp:

  • 739: long delay; proceed?: Y/N
  • 748: slow keypresses; proceed?: Y/N
  • 759: args repeated very often; proceed?: Y/N

browser/BrowserService.cpp:

  • 203: shared encryption key already exists, overwrite?: Y/N Overwrite/Cancel
  • 362: update entry information?: Save/Cancel

gui/DatabaseTabWidget.cpp:

  • 280: is in edit mode, discard changes and close anyway?: Discard/Cancel
  • 297: DB was modified, save changes? Yes/Discard/Cancel Save/Discard/Cancel
  • 387: Disable safe saves and try to save again?: Y/N Disable Safe Saves/Cancel
    • Also considering using just Disable/Cancel
  • 737: Can lock db, since currently editing: Discard(which locks db)/Cancel
  • 750: Locking but DB modified. save before lock?: Save/Discard/Cancel

gui/DatabaseWidget.cpp:

  • 472: delete entry permanently: Y/N Delete/Cancel
  • 490: move entry to recycle bin: Y/N Move to Recycle Bin/Cancel
    • Also considering using just Move/Cancel
  • 623: execute URL if command: Y/N
  • 682: delete group permanently: Y/N Delete/Cancel
  • 691: move group to recycle bin: Y/N Move to Recycle Bin/Cancel
    • Also considering using just Move/Cancel
  • 1248: db file has changed, reload?: Y/N
  • 1271: db file change and you have unsaved changes, merge?: Y/N Merge/Cancel
  • 1489: Empty recycle bin?: Y/N Empty Recycle Bin/Cancel

gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp:

  • 250: very high number of rounds warning argon2: "Understood, keep number"/Cancel
  • 267: very high number of rounds warning aes-kdf: "Understood, keep number"/Cancel

gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp:

  • 178: continue without a password warning: Y/Cancel

gui/EditWidgetIcons.cpp:

  • 441: confirm deletion of icon being used by other entries: Y/N Delete/Cancel

gui/EditWidgetProperties.cpp:

  • 69: Delete plugin data?: Y/Cancel Delete/Cancel

gui/entry/EditEntryWidget.cpp:

  • 809: apply generated pw to entry?: Y/N
  • 932: entry has unsaved changes: Cancel/Save/Discard
  • 1039: confirm removal of attribute: Y/N Remove/Cancel

gui/entry/EntryAttachmentsWidget.cpp:

  • 166: confirm removal of attachment: Y/N Remove/Cancel
  • 213: confirm overwrite of existing file with attachment: Y/N/Cancel Overwrite/Skip/Cancel
    • It would be nice to make "Skip" (currently "No") not appear if there is only one selected attachment to save. I had to hop into the source to figure out what it did because I was exploring this prompt when only one entry was selected. no/skip versus cancel is confusing in that context.
@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Oct 28, 2018

Great suggestions, I agree with them. Wherever possible, prefer the use of a single verb. Sorry for the confusion in earlier comments.

@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Oct 30, 2018

@kneitinger Do you want to introduce the new buttons in a different PR or add to this one?

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Oct 31, 2018

Happy to include them on this PR (makes sense to include it where the discussion was had). A few days ago I started working on a different issue when this was sitting for a while, so I'm going to open that one tonight and then get to the button changes.

kneitinger added some commits Nov 1, 2018

Make prompts for destructive operations use action words on buttons
Replace yes/no, yes/cancel (and other such buttons on prompts that cause
data to be destroyed) use language that indicates the action that it is
going to take. This makes destructive/unsafe and/or irreversible operations
more clear to the user.

Address feedback on PR #2376
@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Nov 7, 2018

@phoerious @droidmonkey I went through and modified all of the prompts

Verification

  • browser/BrowserService.cpp:
    • 203: shared encryption key already exists, overwrite?: Y/N Overwrite/Cancel
      • Do not know how to test
    • 362: update entry information?: Y/N Save/Cancel
      • Do not know how to test
  • gui/DatabaseTabWidget.cpp:
    • 297: DB was modified, save changes? Yes/Discard/Cancel Save/Discard/Cancel
      • Disabled autosave, created a new entry
      • Attempted to close application
      • Correct prompt was displayed
        2018-11-06_22-38-27
    • 387: Disable safe saves and try to save again?: Y/N Disable Safe Saves/Cancel
      • Copied test database to a new directory and opened it
      • Deleted directory that contains new db
      • Attempted to save db 3 times
      • Correct prompt displayed, and upon selecting "Disable", the safe save setting was disabled
        2018-11-06_22-40-57
  • gui/DatabaseWidget.cpp:
    • 472: delete entry permanently: Y/N Delete/Cancel 2018-11-06_22-48-03
    • 490: move entry to recycle bin: Y/N Move to Recycle Bin/Cancel
      2018-11-06_22-47-54
    • 682: delete group permanently: Y/N Delete/Cancel
      2018-11-06_22-47-24
    • 691: move group to recycle bin: Y/N Move to Recycle Bin/Cancel
      2018-11-06_22-48-14
    • 1271: db file change and you have unsaved changes, merge?: Y/N Merge/Cancel
      • cp testdb.kdbx newdb.kdbx
      • Opened testdb.kdbx with autosave disabled
      • Created new entry
      • mv newdb.kdbx testdb.kdbx
      • Correct prompt displayed, and upon selecting "Merge", console log shows successful merge
        2018-11-06_22-45-46
    • 1489: Empty recycle bin?: Y/N Empty Recycle Bin/Cancel
      2018-11-06_22-48-26
  • gui/EditWidgetIcons.cpp:
    • 441: confirm deletion of icon being used by other entries: Y/N Delete/Cancel
      • Create new entry
      • Go to Icon edit tab and select a preexisting custom icon
      • Click "Delete custom icon"
      • Correct prompt displayed, and upon selecting "Delete", custom icon is removed
        2018-11-06_22-55-44
  • gui/EditWidgetProperties.cpp:
    • 69: Delete plugin data?: Y/Cancel Delete/Cancel
      • Do not know how to populate plugin data or test
  • gui/entry/EditEntryWidget.cpp:
    • 1039: confirm removal of attribute: Y/N Remove/Cancel
      • Add new attribute
      • Select new attribute and click "Remove"
      • Correct prompt displayed, and upon selecting "Remove", the attribute is removed
        2018-11-06_22-57-38
  • gui/entry/EntryAttachmentsWidget.cpp:
    • 166: confirm removal of attachment: Y/N Remove/Cancel
      • Edit entry and add two attachments
      • Select one entry and click "Remove"
      • Correct prompt displayed, and upon selecting "Remove", the attachment is removed
      • Repeat above process with multiple attachments and verify correct number and removal
        2018-11-06_22-59-42
    • 213: confirm overwrite of existing file with attachment: Y/N/Cancel Overwrite/Skip/Cancel
      • Edit entry and add two attachments from home directory
      • Select one entry and click "Save"
      • Choose home directory in file select dialog
      • Correct prompt displayed, showing only "Overwrite" and "Cancel", upon selecting overwrite, attachment is saved.
        2018-11-06_23-04-57
      • Select both entries and click "Save"
      • Correct prompt displayed, shown "Overwrite", "Skip", and "Cancel", upon selecting overwrite, attachment is saved, and next file's prompt is shown. Upon selecting skip, attachment is not saved, and next file's prompt is shown. Upon selecting cancel, no more overwrite dialogs are shown, regardless of index of current attachment.
        2018-11-06_23-04-48
@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Nov 7, 2018

Apologies, forgot to update the GUI tests after these changes, I'll take care of that. As for the CodeFactor test, it deemed a function whose only edit I made was removing a single trailing space to be "Complex". I wouldn't mind refactoring it, but do you know of a way to approximate CodeFactor's behavior locally so I don't need to push to see if it's sufficient?

@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Nov 7, 2018

Don't worry about codefactor. It looks like you have a crash occurring in the tests.

Edit: Looks like you did this outside of the MessageBox class. Unfortunately that will make it impossible to fix the GUI tests. MessageBox class allows tests to "set an answer" that is returned immediately after the message is shown. It would be much cleaner to implement a thin wrapper around what you have done with the QMessageBox button adding. If you need help I can provide an example.

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Nov 7, 2018

@droidmonkey Thanks, I noticed the same thing. I opted to do it out of MessageBox due to difficulty coming up with nice generic solutions. I didn't see an issue because nextAnswer didn't seem to be used in src, but now see how its used in the tests. I think I have a couple ideas on how to make this work neatly, but will definitely need some feedback. I'll post what I come up with in a comment.

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Nov 28, 2018

@droidmonkey

I came up with a rough sketch of something that seems to work well: branch: kneitinger/messagebox_revamp

The main focus was making the interactions that happen outside of the MessageBox files (such as in src/guiDatabaseWidget.cpp) just as nice as they were before this PR. Essentially, if we went this route, every QMessageBox::{Ok,Cancel,Save,...} would just need to be changed to MessageBox::{Ok,Cancel,Save,...}. At the same time, new buttons can be added application-wide with just a few lines of code.

Breakdown of the changes:

  • src/gui/MessageBox.h:
    • 28-59: Defines enum that works analogously to QMessageBox::StandardButton
    • 96: Map to store info of button pointers to MessageBox::Button so that return type
      of MessageBox::newQuestion can just be MessageBox::Button instead of a pointer. This is needed so that developers can use conditionals such as if (answer == MessageBox::Overwrite) with buttons other than QMessageBox::StandardButtons
  • src/gui/MessageBox.cpp:
    • 128-198: Create/Add a button that represents the intent of the MessageBox::Button value.
      Since QMessageBox::clickedButton() returns a pointer to a button, we'll use a map
      to track which button corresponds to which MessageBox::Button
    • 75-79: Start building up a QMessageBox to act as a question
    • 81-85: iterate over possible buttons and when a match is found, add it to the QMessageBox
    • 88: show QMessageBox and wait for user input
    • 90-92: Look up the returned pointer to get its corresponding MessageBox::Button and return it.
    • 95-98: Handle case when m_newNextAnswer is set (for guitest)
  • src/gui/DatabaseWidget.cpp:
    • 459-462: shows standard usage. Works just like how MessageBox::Question worked before,
      but with MessageBox::Button buttons instead of QMessageBox::StandardButtons. e.g.
      MessageBox::Ok vs. QMessageBox::Ok
    • 468-473: quick way to show that the new model has setNextAnswer functionality.
      When ran, application does not exit with code 22 meaning the answer was successfully set.

I'd like to hear your thoughts on this. Keep in mind that this is just a rough prototype for illustrating an approach, and a final solution using this approach would be a lot cleaner (possibly using Q_FLAGS on enum, Refactoring for loops out of question(), warning(), ... and into setButtons(), etc.)

Thanks in advance!

@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Nov 28, 2018

Nice! That is exactly what I had in mind. For the button map, is that translation ready? You may need to add the "tr()" wrappers in there. For the Qt standard buttons there might be an elegant way to bring in the text, not sure about that.

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Nov 28, 2018

Awesome! I did get translations working fine, but kept it out to not overcomplicate the proof of concept. Because MessageBox is not a QObject, tr does indeed need to be constructed from direct calls to translate.

Thanks for looking at it, I'll move forward on this refactor!

Edit: regarding grabbing the standard button text: good idea, I'll look into it.

kneitinger added some commits Dec 5, 2018

Refactor MessageBox class to allow for custom buttons
Replaces arguments and return values of type QMessageBox::StandardButton(s)
with MessageBox::Button(s), which reimplements the entire set of
QMessageBox::StandardButton and allows for custom KeePassXC buttons,
such as "Skip". Modifies all calls to MessageBox functions to use
MessageBox::Button(s).

Addresses feedback on #2376
@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Dec 5, 2018

@droidmonkey I worked in the translation on the new buttons as well as using the already translated text from the QT standard buttons. I accrued a CodeFactor issue for the MessageBox::addButton() function, which I'd like to address; as well as some formatting I'd like to improve upon seeing the diff in GitHub. I'll let you know when thats all pushed up 👍

Remove MessageBox::addButton in favor of map lookup
Replaced the switch statement mechanism in MessageBox::addButton with
a map lookup to address CodeFactor Complex Method issue. This has a
side-effect of a small performance/cleanliness increase, as an
extra QPushButton is no longer created/destroyed (to obtain it's label
text) everytime a MessageBox button based on QMessageBox::StandardButton
is created; now the text is obtained once, at application start up.
Show resolved Hide resolved src/main.cpp Outdated
@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Dec 6, 2018

I've moved the initializeButtonDefs() call, and corrected some of the dialogs that I overlooked when merging in the Database Widget refactor commits.

I went through and re-ran my manual tests listed in previous comments, and its working the same, however, in reimplementing the buttons, I've overlooked the icons:

  • Before:
    48116674-af6dc080-e21b-11e8-86ae-1efa18ffd2e5
  • Current:
    2018-12-06_12-30-32_iconlesss

If we want them back, It would not be much work to obtain the icons for StandardButtons in the same way I retrieve the label text for them. Let me know what you think.

Edit: not sure what happened with that Windows build renaming classes...any insight into that?

@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Dec 6, 2018

Oh crap, its because Windows.h is included. You have to add a #undef MessageBox towards the top.

As for the icons, I never noticed them before! They do not appear on Windows.

image

I think they are hideous and distracting anyway.

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Dec 7, 2018

😆 Yeah, I thought the lack of icons was a classier experience, but wasn't sure if they were kept for some reason. I haven't changed my QT theme because I though the icons would stay and look out of place in a better looking theme. After seeing your screenshot, tried changing the theme and wow turns out KeePassXC can look so good with a modern style and less stock icons!

Implemented the #undef and verified it builds and functions correctly on a Windows env I finally set up; thanks for the tip! Unless anyone has any changes they would like to see, I believe the work on this PR is done now!

@kneitinger kneitinger changed the title Add confirmation prompt before moving groups to the recycling bin Add confirmation prompt before moving groups to the recycling bin, Refactor MessageBox to allow custom buttons Dec 7, 2018

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Dec 7, 2018

Updated the PR title and added "Update" section to the top of the PR description to provide some context for any new viewers.

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Dec 14, 2018

Hi all, just a bump that this is ready for further review, or merge if ready 🙂

@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Dec 16, 2018

Yes sorry, had finals this past week. I will address this asap.

@kneitinger

This comment has been minimized.

Copy link
Contributor Author

kneitinger commented Dec 17, 2018

Thanks, congrats on completing your finals!

@droidmonkey droidmonkey merged commit 4d4c839 into keepassxreboot:develop Dec 20, 2018

4 checks passed

CodeFactor 3 issues fixed. 2 issues found.
Details
MacOS (KeepassXC) TeamCity build finished
Details
Ubuntu Linux (KeepassXC) TeamCity build finished
Details
Windows 10 (KeepassXC) TeamCity build finished
Details
@droidmonkey

This comment has been minimized.

Copy link
Member

droidmonkey commented Dec 20, 2018

Thanks! Fantastic work on this PR.

@droidmonkey droidmonkey added this to the v2.4.0 milestone Dec 20, 2018

@droidmonkey droidmonkey added the ux label Dec 20, 2018

droidmonkey added a commit that referenced this pull request Mar 19, 2019

Release 2.4.0
- New Database Wizard [#1952]
- Advanced Search [#1797]
- Automatic update checker [#2648]
- KeeShare database synchronization [#2109, #1992, #2738, #2742, #2746, #2739]
- Improve favicon fetching; transition to Duck-Duck-Go [#2795, #2011, #2439]
- Remove KeePassHttp support [#1752]
- CLI: output info to stderr for easier scripting [#2558]
- CLI: Add --quiet option [#2507]
- CLI: Add create command [#2540]
- CLI: Add recursive listing of entries [#2345]
- CLI: Fix stdin/stdout encoding on Windows [#2425]
- SSH Agent: Support OpenSSH for Windows [#1994]
- macOS: TouchID Quick Unlock [#1851]
- macOS: Multiple improvements; include CLI in DMG [#2165, #2331, #2583]
- Linux: Prevent Klipper from storing secrets in clipboard [#1969]
- Linux: Use polling based file watching for NFS [#2171]
- Linux: Enable use of browser plugin in Snap build [#2802]
- TOTP QR Code Generator [#1167]
- High-DPI Scaling for 4k screens [#2404]
- Make keyboard shortcuts more consistent [#2431]
- Warn user if deleting referenced entries [#1744]
- Allow toolbar to be hidden and repositioned [#1819, #2357]
- Increase max allowed database timeout to 12 hours [#2173]
- Password generator uses existing password length by default [#2318]
- Improve alert message box button labels [#2376]
- Show message when a database merge makes no changes [#2551]
- Browser Integration Enhancements [#1497, #2253, #1904, #2232, #1850, #2218, #2391, #2396, #2542, #2622, #2637, #2790]
- Overall Code Improvements [#2316, #2284, #2351, #2402, #2410, #2419, #2422, #2443, #2491, #2506, #2610, #2667, #2709, #2731]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.