cybersemics / em

A beautiful, minimalistic note-taking app for personal sensemaking.
Other
286 stars 119 forks source link

Drag and drop: Indicate when sorted drop target is out of view #1828

Closed raineorshine closed 2 weeks ago

raineorshine commented 7 months ago

In this screenshot, Colorado is being dragged and hovered over the sorted States context, but the drop target is not visible.

Render an upward pointing blue triangle at the top of screen to communicate to the user that the thought will be inserted above the top of the screeen.

Render a downward pointing blue triangle at the bottom of the screen when the insertion point is below the bottom of the screen (not pictured).

Animate the arrows with a gentle up-and-down bobbing motion.

image


- US
  - States
    - =pin
      - true
    - =sort
      - Alphabetical
        - Asc
    - Alabama
    - Alaska
    - Arizona
    - Arkansas
    - California
    - Connecticut
    - Delaware
    - Florida
    - Georgia
    - Hawaii
    - Idaho
    - Illinois
    - Indiana
    - Iowa
    - Kansas
    - Kentucky
    - Louisiana
    - Maine
    - Maryland
    - Massachusetts
    - Michigan
    - Minnesota
    - Mississippi
    - Missouri
    - Montana
    - Nebraska
    - Nevada
    - New Hampshire
    - New Jersey
    - New Mexico
    - New York
    - North Carolina
    - North Dakota
    - Ohio
    - Oklahoma
    - Oregon
    - Pennsylvania
    - Rhode Island
    - South Carolina
    - South Dakota
    - Tennessee
    - Texas
    - Utah
    - Vermont
    - Virginia
    - Washington
    - West Virginia
    - Wisconsin
    - Wyoming
  - Colorado
Zubair286 commented 2 months ago

When trying to render a downward arrow for a context such as above States, there is an issue with DropHover placement. All the thoughts are not rendered until we scroll down so for a thought that will be placed at the very bottom will not have any DropHover rendered until we scroll down. Is there any way to detect the position of the DropHover on large context when its rendering at the bottom?

raineorshine commented 2 months ago

This isn't so much about the position of the DropHover component. As you pointed out, it is not actually rendered at all in this case. Instead, we need to find where in the list it would be rendered if it were within view. It's a function of the Redux state and hover position.

The logic for positioning within a sorted list is somewhat buried in one of the hooks, but it is ultimately just a function of the Redux state (i.e. it is a selector), so it should be easily extractable. However, determining whether that position is in view or not is trickier, as that depends on which VirtualThoughts are shimmed (i.e. virtualized). I'm not sure off the top of my head the best way to expose this to an indicator arrow like this issue requires. I would suggest identifying and formulating this before beginning an implementation so that we confirm a satisfying approach, i.e. not introducing a bunch of additional state to make this happen.

Zubair286 commented 2 months ago

I tried rendering the arrow by comparing the top value of DropHover with window.innerHeight, it works for the upward arrow but for the downward as you mention it does not render at all.

raineorshine commented 2 months ago

Yeah. VirtualThought is responsible for which thoughts are in the viewport, so I would look there.

Zubair286 commented 2 months ago

Right, and I believe getSortedRank is the selector that returns the sorted rank to position the thought but it does not seem to be setting any position for the hover or thought.

Zubair286 commented 2 months ago

One thing that comes to my mind is to get all the children of the context, assuming it only returns the children that are currently rendered, and calculate the rank using getSortedRank. By comparing the rank with the rank of the last thought of context we might be able to identify the need to render the arrow. At least for rendering the downward arrow.

raineorshine commented 2 months ago

There isn't a straightforward function to get a list of children that are contained within the viewport, but that information is indirectly accessible within the LayoutTree. Five thoughts are rendered below the bottom of the viewport to avoid rendering gaps as thoughts are virtualized/unvirtualized during scrolling:

https://github.com/cybersemics/em/blob/e321a6537f53eb9e3bfa3108195ebc94cd622295/src/components/LayoutTree.tsx#L433-L434

The render loop in LayoutTree short circuits as soon as viewportBottom is exceeded:

https://github.com/cybersemics/em/blob/e321a6537f53eb9e3bfa3108195ebc94cd622295/src/components/LayoutTree.tsx#L661-L666

LayoutTree seems like the right place for the arrow logic since it is the component that is aware of the overall layout and rendering of all the visible thoughts. The challenge is that the sorted context hover logic is buried in the DropHover components which is only rendered in visible thoughts.

Zubair286 commented 2 months ago

In the LayoutTree component, the arrow positioning was not remaining sticky to the top and bottom, I first tried doing it in LayoutTree but it didn't work probably because of the containers height. It seems to be placed fine in Content component but it does not have the awareness of LayoutTree component.

Zubair286 commented 2 months ago

Yes, the sorted context hover logic is what I need instead of using the DropHover position which is not reliable.

raineorshine commented 2 months ago

In the LayoutTree component, the arrow positioning was not remaining sticky to the top and bottom, I first tried doing it in LayoutTree but it didn't work probably because of the containers height. It seems to be placed fine in Content component but it does not have the awareness of LayoutTree component.

It shouldn't matter which component it goes in since it is rendered position: fixed (or position: absolute + scrollTop to make it work on iPhone when the keyboard is up).

Zubair286 commented 2 months ago

I have examined the DropHover and indeed it only works for the rendered thoughts, and the moveThought action actually moves the thought by setting the new rank using getSortedRank, but in this I do not understand how for example a thought that will go to the bottom of the entire list is placed, is there a condition that checks if it is going below the rendered items? Maybe I am missing something but without knowing where in the list the DropHover would render this is tricky.

raineorshine commented 2 months ago

If getSortedRank is less than the rank of the first thought in the viewport, then the drop location is above the top of the viewport.

If getSortedRank is more than the rank of the last thought in the viewport, then the drop location is below the bottom of the viewport.

Zubair286 commented 2 months ago

Right, so that brings us to finding out the first and last thought in the viewport.