Skip to content

Commit

Permalink
[desk-tool] Improve timeline UI
Browse files Browse the repository at this point in the history
  • Loading branch information
mariuslundgard authored and rexxars committed Oct 6, 2020
1 parent 164dd3d commit f48279a
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,12 @@ export function DocumentPane(props: DocumentPaneProps) {
[historyController, setRange, setTimelineMode]
)

const loadMoreHistory = useCallback((state: boolean) => {
historyController.setLoadMore(state)
}, [])
const loadMoreHistory = useCallback(
(state: boolean) => {
historyController.setLoadMore(state)
},
[historyController]
)

const handleTimelineClose = useCallback(() => {
setTimelineMode('closed')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,19 @@
box-sizing: border-box;
max-height: calc(100vh - 198px);
}

.list {
list-style: none;
padding: 0;
margin: 0;
}

.loading {
padding: var(--medium-padding);
text-align: center;
color: var(--gray);

@nest .list + & {
border-top: 1px solid #cad1dc;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/* eslint-disable react/prop-types */

import React, {useCallback, forwardRef, useRef} from 'react'
import React, {forwardRef, useCallback, useEffect, useRef} from 'react'
import {Chunk} from '@sanity/field/diff'
import {Timeline as TimelineModel} from '../documentHistory/history/timeline'
import VisibilityContainer from './visibilityContainer'
import {TimelineItem} from './timelineItem'
import {TimelineItemState} from './types'

Expand Down Expand Up @@ -37,59 +34,102 @@ export function revTimelineProps(rev: Chunk) {
}
}

// Must be a positive number
const LOAD_MORE_OFFSET = 200

export const Timeline = forwardRef<HTMLDivElement, TimelineProps>(
(
{timeline, disabledBeforeSelection, topSelection, bottomSelection, onSelect, onLoadMore},
ref
) => {
const visibilityContainerRef = useRef<VisibilityContainer | null>(null)

const handleScroll = useCallback(() => {
visibilityContainerRef.current?.recalculate()
}, [visibilityContainerRef.current])
const rootRef = useRef<HTMLDivElement | null>(null)
const loadingRef = useRef<HTMLDivElement | null>(null)
const listRef = useRef<HTMLOListElement | null>(null)

let state: TimelineItemState = disabledBeforeSelection ? 'disabled' : 'enabled'

const loadMoreIfNeeded = useCallback(() => {
const rootEl = rootRef.current
const loadingEl = loadingRef.current

if (loadingEl && rootEl) {
const {offsetHeight, scrollTop} = rootEl
const bottomPosition = offsetHeight + scrollTop + LOAD_MORE_OFFSET
const isVisible = loadingEl.offsetTop < bottomPosition

if (isVisible) onLoadMore(isVisible)
}
}, [onLoadMore])

// This is needed because we set the reference element both for
// the provided `ref` from `forwardRef`, and the local `rootRef`.
const setRef = useCallback(
(el: HTMLDivElement | null) => {
if (ref) {
if (typeof ref === 'function') ref(el)
if (typeof ref === 'object') (ref as any).current = el
}
rootRef.current = el
},
[ref]
)

useEffect(loadMoreIfNeeded, [loadMoreIfNeeded])

// On mount: Scroll to selected timeline item
useEffect(() => {
const selectedEl = listRef.current?.querySelector('[data-state="selected"]')

if (selectedEl) {
window.requestAnimationFrame(() => {
selectedEl.scrollIntoView({block: 'center'})
})
}
}, [])

return (
<div className={styles.root} ref={ref} onScroll={handleScroll}>
{timeline.mapChunks(chunk => {
const isSelectionTop = topSelection === chunk
const isSelectionBottom = bottomSelection === chunk

if (isSelectionTop) {
state = 'withinSelection'
}

if (isSelectionBottom) {
state = 'selected'
}

const item = (
<TimelineItem
key={chunk.id}
chunk={chunk}
isSelectionBottom={isSelectionBottom}
isSelectionTop={isSelectionTop}
state={state}
onSelect={onSelect}
title={chunk.id}
type={chunk.type}
timestamp={chunk.endTimestamp}
/>
)

// Flip it back to normal after we've rendered the active one.
if (state === 'selected') {
state = 'enabled'
}

return item
})}
<div className={styles.root} ref={setRef} onScroll={loadMoreIfNeeded}>
<ol className={styles.list} ref={listRef}>
{timeline.mapChunks(chunk => {
const isSelectionTop = topSelection === chunk
const isSelectionBottom = bottomSelection === chunk

if (isSelectionTop) {
state = 'withinSelection'
}

if (isSelectionBottom) {
state = 'selected'
}

const item = (
<li key={chunk.id}>
<TimelineItem
chunk={chunk}
isSelectionBottom={isSelectionBottom}
isSelectionTop={isSelectionTop}
state={state}
onSelect={onSelect}
title={chunk.id}
type={chunk.type}
timestamp={chunk.endTimestamp}
/>
</li>
)

// Flip it back to normal after we've rendered the active one.
if (state === 'selected') {
state = 'enabled'
}

return item
})}
</ol>

{!timeline.reachedEarliestEntry && (
<VisibilityContainer ref={visibilityContainerRef} padding={20} setVisibility={onLoadMore}>
<div>Loading...</div>
</VisibilityContainer>
<div className={styles.loading} ref={loadingRef}>
Loading events…
</div>
)}
</div>
)
Expand Down

This file was deleted.

0 comments on commit f48279a

Please sign in to comment.