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

Allow toggling patch checkbox by clicking on lines #1429

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

simonwiles
Copy link
Contributor

This PR changes the line-select UI in patch mode to allow toggling lines by clicking anywhere on the line. It doesn't address all the desired improvements described in #1421 (and so won't close it), but I've been using this modification for a while and it does make things somewhat easier.

If it passes an initial review I can update the CHANGELOG.md and bump the version number.

Peek 2020-09-23 08-31

@wmertens
Copy link
Contributor

Would it be possible as a quick win to add a button for toggling all? Just an idea, not worth blocking this over.

Copy link
Contributor

@wmertens wmertens left a comment

Choose a reason for hiding this comment

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

Knockout novice but LGTM

@simonwiles
Copy link
Contributor Author

Would it be possible as a quick win to add a button for toggling all? Just an idea, not worth blocking this over.

Yeah, I like the idea of an "invert selection" button, or something like that. I'm not going to be able to do it right now, but I'll make a note. I might look at addressing actual drag selecting if I can find time too, but I'd rather see this closed off first, because with the best will in the world I don't know if/when I'll have time!

Knockout novice but LGTM

Likewise, but tbh this is more about getting the regex find/replace right :)

Is the advice at https://github.com/FredrikNoren/ungit/blob/master/CONTRIBUTING.md#pull-requests still valid? I notice the release process seems to have changed a bit since this was written. I've updated the changelog under "unreleased" and not touched the version number in package.json -- hope that's okay.

@@ -198,7 +208,7 @@ class TextDiffViewModel {
}
return `<div class="d2h-code-line-prefix"><span data-bind="visible: editState() !== 'patched'">${symbol}</span><input ${
isActive ? 'checked' : ''
} type="checkbox" data-bind="visible: editState() === 'patched', click: togglePatchLine.bind($data, ${index})"></input></div>`;
} type="checkbox" data-bind="visible: editState() === 'patched', click: togglePatchLine.bind($data, ${index}), clickBubble: false"></input></div>`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the checkbox is inside the row, this click event on the checkbox is not really needed, right?

Copy link
Contributor Author

@simonwiles simonwiles Sep 26, 2020

Choose a reason for hiding this comment

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

Well, it's needed in the sense that it's invoked here:

toggleCheckboxFromRowClick(c, e) {
if (this.editState() === 'patched')
e.currentTarget.querySelector('input[type=checkbox]').click();
}

and simply removing it means the functionality no longer works :)

But yes, I agree it feels like it should be redundant (I originally made these changes as a quick-and-dirty hack for my own use some months ago, but the conversation at #1421 prompted my to PR them).

The alternative, it seems to me, is to bind the "checked" state of the checkbox to the index in TextDiffViewModel.patchLineList and bind togglePatchLine(index) to the click event on the <td/> -- so I've added a commit that does that.

There are two downsides to this: 1) is that, as the comment at

// ko's binding resolution is not recursive, which means below ko.bind refresh method doesn't work for
// data bind at getPatchCheckBox that is rendered with "html" binding.
// which is reason why manually updating the html content and refreshing kobinding to have it render...
mentions, it is necessary to force a re-render -- this makes the behaviour a little awkward in the edge case where the user tries to select text on a patchable line when patch-mode is enabled; and 2) is that the data-bind="checked: ... binding is two-way, so the normal behaviour of the checkbox has to be inhibited otherwise a) the checkbox is changed and the immediately re-changed, and b) togglePatchLine(index) is only called once, and the interface ends up in an inconsistent state. I've addressed that by simply applying point-events: none to the checkbox itself (making the checked binding effectively one-way), but there may be a better way?

All in all, I'm not 100% convinced this is better than before -- what do you think?

components/textdiff/textdiff.less Outdated Show resolved Hide resolved
components/textdiff/textdiff.js Outdated Show resolved Hide resolved
components/textdiff/textdiff.less Outdated Show resolved Hide resolved
simonwiles added a commit to simonwiles/ungit that referenced this pull request Oct 2, 2020
The change introduced [here](FredrikNoren#1429 (comment)) (moving the click event to the row) requires a re-render when the row is clicked, and this causes selection issues when selecting text on a single line only.  This commit prevent the handler from running when patch mode is not enabled, and so makes selections from a single line possible at least in this case.
@simonwiles
Copy link
Contributor Author

simonwiles commented Oct 2, 2020

The numberOfSelectedPatchLines variable was only used to reset the edit state when it evaluated to zero, so I dropped it and replace the check with a test against this.patchLineList().filter(Boolean).length (see 825e120).

@simonwiles
Copy link
Contributor Author

simonwiles commented Oct 2, 2020

Important:

When investigating this, I noticed a bug in the way that ungit operates in the present release version (1.5.11). See below for a quick demonstration:

Peek 2020-10-01 22-56

The reason this occurs is that after all the checkboxes are cleared, the edit state is set to none, and they are hidden; when the patch button is clicked again, the array returned by patchLineList() is reinitialized to all true, but the UI is not re-rendered, so the boxes all appear empty. Clicking one inverts its state, and so what gets committed as the patch is the inverse of the UI state. Note too that not moving the mouse for 3 seconds and then moving it again causes the UI to re-render (because of

ungit/public/source/main.js

Lines 115 to 125 in 9374f6c

(function detectReActivity() {
var lastMoved = Date.now();
document.addEventListener('mousemove', function () {
// If the user didn't move for 3 sec and then moved again, it's likely it's a tab-back
if (Date.now() - lastMoved > 3000) {
console.log('Fire change event due to re-activity');
programEvents.dispatch({ event: 'working-tree-changed' });
}
lastMoved = Date.now();
});
})();
):

Peek 2020-10-01 23-03

This became much more obvious with the earlier commits in this PR, because the diff (and checkboxes) are re-rendered on click:

Peek 2020-10-01 22-14

My choice to address this was to make the diff re-render on each render (i.e. not checking for a difference in the html string, as this check is inadequate to determine if the state of the checkboxes has changed anyway). This doesn't generate more re-renders or cause any thrashing, and fixes the issue.

@@ -98,9 +98,14 @@
}
}

.d2h-code-linenumber {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
.d2h-code-linenumber {
.d2h-code-linenumber, .d2h-code-side-linenumber {

}

if (html !== this.htmlSrc) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Unfortunatley this makes selecting lines a bit hard. Because the selection gets lost after the detectReActivity mouse move delay...

Maybe we will need the checked attribute on the checkbox again to force the HTML to be different in your described scenario?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunatley this makes selecting lines a bit hard. Because the selection gets lost after the detectReActivity mouse move delay...

Yeah, that's an additional problem I hadn't noticed, thanks (sigh).

Maybe we will need the checked attribute on the checkbox again to force the HTML to be different in your described scenario?

This won't work, as demonstrated by the fact that the bug I described in my last post is present in the v1.5.11 release where this attribute is still written. The html == this.htmlSrc check compares the html the last time the render function ran against the newly generated html -- in the scenario above, the editState is set to 'none' when all the boxes are unchecked, and the patchLineList array is emptied (not set to all false, for example), and so the TextDiffViewModel.render method recreates it will all values set to true. The result is the html string contains all checked attributes on all checkboxes, and is identical to the last time the component rendered.

I'll give it some more thought; the whole way the patchLineList is connected with the editState and instantiated in textdiff.js and invalidated in staging.js could stand a rethink, and I'm starting to get far more familiar with the architecture here and also knockout.js than I ever intended.

Fwiw none of these problems, except the bug already in production, exist with my original PR as submitted, and I'll be continuing to use it that way locally, as I have been for months now.

@simonwiles
Copy link
Contributor Author

simonwiles commented Oct 3, 2020

Okay, this last commit addresses the bug in production, I think (although it's really a workaround rather than a proper fix).

Browser HTML-parsers are tolerant, but still...
Note that this actually *allows* clicking on line numbers to toggle checkboxes in patch mode.
This is mainly just because it's the easiest way to satisfy the linter...
The change introduced [here](FredrikNoren#1429 (comment)) (moving the click event to the row) requires a re-render when the row is clicked, and this causes selection issues when selecting text on a single line only.  This commit prevent the handler from running when patch mode is not enabled, and so makes selections from a single line possible at least in this case.
Prettier seems to insist on this on one line.
When the edit state has been reset because all the patch line checkboxes have been cleared, `patchLineList` is reset to an empty list.  Comparing the before and after html strings is not adequate to detect this change, because `TextDiffViewModel.render` always renders with patchLineList initialized to all `true` values regardless.  Adding this check prevents the UI state from becoming inconsistent, while not rerendering on the `detectReActivity` event.
This isn't perfect, but it's better than it was before.
@wmertens
Copy link
Contributor

I wish that you could click and drag (or tap + hold and drag) to select/unselect lines, and that there was a toggle select button like the toggle files button, but this PR is already a great improvement and I'd rather not wait for those improvements.

@campersau I tried it out, it merged cleanly and LGTM, would you consider merging?

@simonwiles
Copy link
Contributor Author

I'm just getting around to rebasing all my local changes on top of the most recent release, and I realized this PR is still open. Might there be any interest in revisiting this?

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.

4 participants