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

Bidirectional Linking #622

Merged
merged 12 commits into from
Sep 25, 2023
Merged

Bidirectional Linking #622

merged 12 commits into from
Sep 25, 2023

Conversation

jvorob
Copy link
Contributor

@jvorob jvorob commented Aug 11, 2023

Adds bidirectional linking (same-table cursor-links)

Allows linking sections in a cycle as long as all are same-table cursor links
(e.g. A->B->A, A->B->C->A, etc)

Clicking/moving cursor in any of the linked sections updates them all.
Works correctly even if sections have differing filters

Tests now check that same-table cursor cycles are allowed in selectBy,
but others are forbidden.

No actual tests of behavior, though

@jvorob jvorob added the preview Launch preview deployment of this PR label Aug 11, 2023
@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-09-10T18:01:44.067Z)

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-09-13T18:25:46.351Z)

@jvorob jvorob force-pushed the link-bidirectional branch from 7142cd0 to 671f889 Compare August 14, 2023 18:42
@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-09-13T18:43:06.709Z)

@jvorob jvorob force-pushed the link-bidirectional branch from 671f889 to 2de08cb Compare September 9, 2023 01:56
@github-actions
Copy link
Contributor

github-actions bot commented Sep 9, 2023

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-09T01:59:22.195Z)

@jvorob jvorob added preview Launch preview deployment of this PR and removed preview Launch preview deployment of this PR labels Sep 12, 2023
@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-12T08:29:16.785Z)

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-12T09:12:17.523Z)

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-12T09:28:05.780Z)

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-12T11:47:43.741Z)

@jvorob jvorob marked this pull request as ready for review September 12, 2023 11:52
@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-12T11:53:33.548Z)

@berhalak berhalak self-requested a review September 12, 2023 22:01
app/client/components/Cursor.ts Outdated Show resolved Hide resolved
app/client/components/Cursor.ts Outdated Show resolved Hide resolved
app/client/components/Cursor.ts Outdated Show resolved Hide resolved
app/client/components/Cursor.ts Outdated Show resolved Hide resolved
app/client/components/Cursor.ts Outdated Show resolved Hide resolved
app/client/components/LinkingState.ts Outdated Show resolved Hide resolved
// (i.e. two sections back) instead of looking at our srcSection's activeRowId. This way it doesn't matter how
// section B is filtered, since we're getting our cursorPos straight from A (through a computed in B.LS)
//
// However, each linkingState needs to decide whether to use the cursorPos from the srcSec (i.e. its activeRowId),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why it neeeds to decide it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As in:

the linkingState can take cursorPos from this.srcSection or this.srcSection.linkingState

If the srcSection is "driving" the linking, i.e. was most recently clicked, then we use it (e.g. B.LS pulls from its srcSection directly).
Otherwise the driver is further up the chain, so we use srcSection.linkingState to get to it (e.g. C.LS pulls from its srcSection.linkingState, since B isn't the driver of this chain)

Whichever it is, there needs to be some logic that decides which to use, is what I was trying to say.

Does that explain it better? And/or is the original explanation misleading?

app/client/components/LinkingState.ts Outdated Show resolved Hide resolved
//linked section (srcSection) and all upstream sections (through srcSection.linkingState)
this.incomingCursorPos = this.autoDispose((ko.computed(() => {
// Get previous linkingstate's info
// NOTE: prevlink = this.srcSec.linkingState is 2 hops back, (i.e. it reads from from this.srcSec.linkSrcSec)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// NOTE: prevlink = this.srcSec.linkingState is 2 hops back, (i.e. it reads from from this.srcSec.linkSrcSec)
// NOTE: prevlink = this.srcSec.linkingState is 2 hops back, (i.e. it reads from this.srcSec.linkSrcSec)

Can you rewrite it and add more explanation I don't follow 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.

rewritten, is the new one better?

app/client/components/LinkingState.ts Outdated Show resolved Hide resolved
@berhalak
Copy link
Contributor

Have you thought about the UI changes it should bring along?

@berhalak
Copy link
Contributor

berhalak commented Sep 14, 2023

When I have a filtered section, and the cursor "vanishes" becuase it was set to a filtered our row, and then I click on the same row as it was before, the position is not updated. In screenshot, section 4 is selected by 1, but the cursor is not updated

image

When deleting and undoing, the cursor is not updated, or wrongly reflect the state

image

https://grist-link-bidirectional.fly.dev/wFKyLXrPdX1z/jarek/p/38

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-14T19:37:57.824Z)

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-19T07:06:47.704Z)

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-19T07:11:53.712Z)

@jvorob
Copy link
Contributor Author

jvorob commented Sep 19, 2023

RE: Have I thought about UI changes it should bring

  • SelectBy needs to show cycles so you can select them in the first place (already done as part of this PR)

  • link info (in rightPanel) could maybe better indicate that things are cyclically linked instead of the current thing which just says "Link from section A sets cursor to [blah]"? Maybe could indicate which section was last clicked in / is driving the link? But that's non-trivial / might require walking the graph, and it's unclear if that's even a useful feature, so I don't think it needs to be in this diff

  • Some way to show when rows are filtered out, especially now that cursor-linking can leapfrog a section, which might be very confusing to users. However, there's nowhere in the UI to pus this at the moment. I think it would go well with the link-state bubbles I was working on earlier this summer, but they got sidelined since we couldn't get consensus on UI. However, I'm going to clean up and re-post that diff before I'm done here, so hopefully the code will all be there once people decide where to display the UI, and that would be a very natural place to indicate broken cursor-linking due to filtering (e.g. the linkstate bubble goes red, on hover it explains that "I'm trying to show this row because of cursor linking, but it's filtered out here")

@berhalak berhalak self-requested a review September 19, 2023 08:23
@jperon
Copy link

jperon commented Sep 19, 2023

@jvorob Thank you very much for this work, which would be really handy!
With such a feature, it would be nice to enable the "select widget" on cards that are linked that way ; otherwise, they can’t really be used as selector, as can be viewed on this sample:

CardSelectWidget

app/client/ui/selectBy.ts Outdated Show resolved Hide resolved
app/client/ui/selectBy.ts Outdated Show resolved Hide resolved
app/client/components/Cursor.ts Outdated Show resolved Hide resolved
app/client/components/Cursor.ts Show resolved Hide resolved

it('should allow creating cursor-linked-cycles', async function() {
assert.equal(await driver.findContent('.test-select-row', /Performances detail/).isPresent(), true);

Copy link
Contributor

Choose a reason for hiding this comment

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

But I think we don't have any tests that cover this bi-directional linking, nothing that is testing that it actually works?

// NOTE: we're guaranteed to hit target before the end of the array (because of the `if(...includes...)` above)
// ALSO NOTE: isAncestorSameTableCursorLink may be 1 shorter than ancestors, but it's accounted for by the above
let i = 0;
for (;;) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Clever :D Eslint is tricked, but it is my fault, I don't know how to configure rules to cover this case. Still, though, some safe guard for the number of loops would be good to add, like while (i < 10000) {} if (i >= 10000) throw new Error()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, there's already a if i == source.ancestors.length - 1 { throw Error } in the body of the loop to keep it from going infinite. Maybe I could change it to if i >= to make it a little safer? But I think trying to push that condition into the while would be ugly, since the success condition is breaking out of the loop. Would have to do some sort of thing with a successFlag = false, if(successCondition) { successFlag = true; break; } ... /* after loop */ if(!successFlag) { throw Error }, whereas the current code already covers it. Do you think it's acceptable to leave it as is or should I force that condition into the while?

@berhalak
Copy link
Contributor

@jvorob Thank you very much for this work, which would be really handy! With such a feature, it would be nice to enable the "select widget" on cards that are linked that way ; otherwise, they can’t really be used as selector, as can be viewed on this sample:

Hi @jperon,

Can you explain what you have in mind by "on cards". The "Select By" is enabled for all kind of widgets by default, so a "Card list" widget is supported out of box. A single "Card Widget" is also supported, but as it doesn't have navigation buttons, this feature wouldn't make much sense there.

@dsagal
Copy link
Member

dsagal commented Sep 21, 2023

Can you explain what you have in mind by "on cards". The "Select By" is enabled for all kind of widgets by default, so a "Card list" widget is supported out of box. A single "Card Widget" is also supported, but as it doesn't have navigation buttons, this feature wouldn't make much sense there.

A Card widget sometimes has navigation buttons, so maybe @jperon is suggesting to enable those in this case?

image

@jperon
Copy link

jperon commented Sep 21, 2023

@dsagal You have exactly seen what I meant.

@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-23T04:05:10.501Z)

Copy link
Contributor

@berhalak berhalak 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 @jvorob !

Previously we'd set cursorPos=null if linked cursorPos was outdated
relative to us. However, this caused issues (little arrow would
disappear, wouldn't get set correctly when first setting selectBy)

Removing it seems to work fine, since observables prevent looping
and version keeps cycle terminated correctly
Previously I updated cursorEdited on rowIndex() change

However, this ran into the exact same bug that properRowId was
created for, namely that if the data changes (filtered out), then
rowIndex won't update. Switched the dependency to properRowId
seems to fix it.

Also, weird timing/dependency stuff, need to subscribe version
AFTER subscribing activeRowId
We now want to disallow most cycles, but allow same-table
cursor-linked cycles

Note: I tested that filtered cycles are forbidden,
same-table-cursor cycles are allowed,

but I never checked if different-table cursor cycles are forbidden
might be something to add, but would require more fiddling
Also removed cursorPos null tweak
christ that's a whole paper in there
If several sections cyclically cursor-linked, and a row
was deleted, the sections would go to different places.

(default behavior on row delete is jump to row 0, but active
section's cursor explicitly keeps its index on row-delete.
The setCursorPos from this WAS actually called last, so should
have correctly set cursorEdited(), but didn't get called)
@github-actions
Copy link
Contributor

Deployed as https://grist-link-bidirectional.fly.dev (until 2023-10-25T22:22:14.701Z)

@jvorob jvorob merged commit 29f07a8 into main Sep 25, 2023
13 checks passed
@jvorob jvorob deleted the link-bidirectional branch September 25, 2023 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
preview Launch preview deployment of this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants