Skip to content

Commit

Permalink
Add indicator arrow on top/bottom of viewport for drop positions outs…
Browse files Browse the repository at this point in the history
…id e of viewport
  • Loading branch information
Zubair286 committed Sep 5, 2024
1 parent 74e4973 commit c7835b1
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 5 deletions.
6 changes: 6 additions & 0 deletions panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ export default defineConfig({
_test: '0s',
},
},
arrowBobbleAnimation: {
value: {
base: '2000ms',
_test: '0s',
},
},
},
},
},
Expand Down
9 changes: 9 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -2712,3 +2712,12 @@ a.advance-setting-link {
.copy-icon-wrapper svg {
cursor: pointer;
}

@keyframes bobble {
0%, 100% {
transform: translateX(-50%) translateY(0);
}
50% {
transform: translateX(-50%) translateY(10px);
}
}
90 changes: 87 additions & 3 deletions src/components/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import classNames from 'classnames'
import React, { FC, useMemo, useRef, useState } from 'react'
import React, { FC, useCallback, useMemo, useRef, useState } from 'react'
import { useDragDropManager } from 'react-dnd'
import { useDispatch, useSelector } from 'react-redux'
import { token } from '../../styled-system/tokens'
import Dispatch from '../@types/Dispatch'
import SimplePath from '../@types/SimplePath'
import { Thunk } from '../@types/Thunk'
Expand All @@ -10,16 +12,22 @@ import { toggleColorPickerActionCreator as toggleColorPicker } from '../actions/
import { isTouch } from '../browser'
import { ABSOLUTE_PATH, HOME_PATH, TUTORIAL2_STEP_SUCCESS } from '../constants'
import * as selection from '../device/selection'
import attributeEquals from '../selectors/attributeEquals'
import { childrenFilterPredicate, filterAllChildren } from '../selectors/getChildren'
import getSetting from '../selectors/getSetting'
import getSortedRank from '../selectors/getSortedRank'
import getThoughtById from '../selectors/getThoughtById'
import isTutorial from '../selectors/isTutorial'
import store from '../stores/app'
import viewportStore from '../stores/viewport'
import fastClick from '../util/fastClick'
import head from '../util/head'
import isAbsolute from '../util/isAbsolute'
import parentOf from '../util/parentOf'
import publishMode from '../util/publishMode'
import Editable from './Editable'
import EmptyThoughtspace from './EmptyThoughtspace'
import LayoutTree from './LayoutTree'
import LayoutTree, { TreeThoughtPositioned } from './LayoutTree'
import Search from './Search'

const transientChildPath = ['TRANSIENT_THOUGHT_ID'] as SimplePath
Expand Down Expand Up @@ -47,6 +55,66 @@ const Content: FC = () => {
return children.length
})
const isAbsoluteContext = useSelector(state => isAbsolute(state.rootContext))
const visibilityRef = useRef<'above' | 'below' | null>(null)
const hoveringPath = useSelector(state => state.hoveringPath)
const contextParentPath = parentOf(hoveringPath || [])

const isSortedContext = useSelector(state => {
return attributeEquals(state, head(contextParentPath), '=sort', 'Alphabetical')
})

const dragDropManager = useDragDropManager()
const monitor = dragDropManager.getMonitor()

const item = monitor.getItem()

const sourceThought = useSelector(state => {
const sourceThoughtId = head(item?.path || [])
const sourceThought = getThoughtById(state, sourceThoughtId)
return sourceThought
})

const newRank = useSelector(state => getSortedRank(state, head(contextParentPath), sourceThought?.value || ''))
const scrollY = window.scrollY
const viewportHeight = viewportStore.useSelector(state => state.innerHeight)
// The arbitrary space above the first thought
const spaceAbove = 100

/** Calculate the visibiltiy of thought being dragged in a sorted context. */
const calculateHoverArrowPosition = useCallback(
(thoughts: TreeThoughtPositioned[]) => {
const state = store.getState()
visibilityRef.current = null

let lastSortedThought: TreeThoughtPositioned | null = null

thoughts.some((thought, index) => {
const path = thought.path
const currentThought = getThoughtById(state, head(path))
lastSortedThought = isSortedContext ? thought : null

if (isSortedContext && Math.floor(newRank) === currentThought.rank) {
const y = thought.y

if (y > viewportHeight + scrollY) {
visibilityRef.current = 'below'
} else if (y < scrollY - spaceAbove) {
visibilityRef.current = 'above'
}

return true
}

if (lastSortedThought && lastSortedThought.y > viewportHeight + scrollY) {
visibilityRef.current = 'below'
return true
}

return false
})
},
[isSortedContext, newRank, scrollY, viewportHeight],
)

/** Removes the cursor if the click goes all the way through to the content. Extends cursorBack with logic for closing modals. */
const clickOnEmptySpace: Thunk = (dispatch: Dispatch, getState) => {
Expand Down Expand Up @@ -91,6 +159,22 @@ const Content: FC = () => {
{...fastClick(() => dispatch(clickOnEmptySpace))}
onMouseDown={() => setIsPressed(true)}
>
{isSortedContext && visibilityRef.current && (
<div
style={{
width: '0',
height: '0',
borderLeft: '10px solid transparent',
borderRight: '10px solid transparent',
position: 'fixed',
left: '50%',
transform: 'translateX(-50%)',
borderBottom: '20px solid rgb(155, 170, 220)',
...(visibilityRef.current === 'below' && { bottom: '80px', rotate: '180deg' }),
animation: `bobble ${token('durations.arrowBobbleAnimation')} infinite`,
}}
></div>
)}
{search != null ? (
<Search />
) : (
Expand All @@ -101,7 +185,7 @@ const Content: FC = () => {
TransientEditable
) : (
/* <Subthoughts simplePath={isAbsoluteContext ? ABSOLUTE_PATH : HOME_PATH} expandable={true} /> */
<LayoutTree />
<LayoutTree calculateThoughtPosition={calculateHoverArrowPosition} />
)}
</>
)}
Expand Down
9 changes: 7 additions & 2 deletions src/components/LayoutTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type TreeThought = {
}

/** 2nd Pass: A thought with position information after its height has been measured. */
type TreeThoughtPositioned = TreeThought & {
export type TreeThoughtPositioned = TreeThought & {
cliff: number
height: number
singleLineHeightWithCliff: number
Expand Down Expand Up @@ -347,7 +347,7 @@ const linearizeTree = (
}

/** Lays out thoughts as DOM siblings with manual x,y positioning. */
const LayoutTree = () => {
const LayoutTree = ({ calculateThoughtPosition }: { calculateThoughtPosition: any }) => {
const { sizes, setSize } = useSizeTracking()
const treeThoughts = useSelector(linearizeTree, _.isEqual)
const fontSize = useSelector(state => state.fontSize)
Expand Down Expand Up @@ -591,6 +591,11 @@ const LayoutTree = () => {
// get the scroll position before the render so it can be preserved
const scrollY = window.scrollY

// When a thought is hovered over a sorted context, determine the position of arrow.
useEffect(() => {
calculateThoughtPosition(treeThoughtsPositioned)
}, [calculateThoughtPosition, treeThoughtsPositioned])

// when spaceAbove changes, scroll by the same amount so that the thoughts appear to stay in the same place
useEffect(
() => {
Expand Down

0 comments on commit c7835b1

Please sign in to comment.