Skip to content

Commit

Permalink
feat(flows): draggable results panels (#18460)
Browse files Browse the repository at this point in the history
* feat: WIP drag to resize results

* refactor: WIP make dragging smoother

* refactor: make resizing cells nice and smooth

* refactor: move height/visibility update handlers into results component

* refactor: use vanilla Math instead of lodash

* refactor: consolidate visibility and resizing code in Resizer component

* refactor: rename resizer components to be more generic

* fix: use updated component name

* refactor: use PipeData type instead of any

* chore: misc cleanup
  • Loading branch information
alexpaxton authored Jun 11, 2020
1 parent 7d4d08f commit c6ad3cb
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 133 deletions.
149 changes: 149 additions & 0 deletions ui/src/notebooks/pipes/Query/Resizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Libraries
import React, {FC, useRef, useEffect, ReactNode, useState} from 'react'
import classnames from 'classnames'

// Components
import ResizerHeader from 'src/notebooks/pipes/Query/ResizerHeader'

// Types
import {Visibility} from 'src/notebooks/pipes/Query'
import {PipeData} from 'src/notebooks/index'

interface Props {
data: PipeData
onUpdate: (data: any) => void
children: ReactNode
resizingEnabled: boolean
}

const MINIMUM_RESULTS_PANEL_HEIGHT = 100

const Resizer: FC<Props> = ({data, onUpdate, children, resizingEnabled}) => {
const height = data.resultsPanelHeight
const visibility = data.resultsVisibility

const [size, updateSize] = useState<number>(height)
const [isDragging, updateDragging] = useState<boolean>(false)
const resultsBodyRef = useRef<HTMLDivElement>(null)
const dragHandleRef = useRef<HTMLDivElement>(null)

const resultsBodyClassName = classnames('notebook-raw-data--body', {
[`notebook-raw-data--body__${visibility}`]: resizingEnabled && visibility,
})

const updateResultsStyle = (): void => {
if (resultsBodyRef.current && resizingEnabled && visibility === 'visible') {
resultsBodyRef.current.setAttribute('style', `height: ${size}px`)
} else {
resultsBodyRef.current.setAttribute('style', '')
}
}

const handleUpdateVisibility = (resultsVisibility: Visibility): void => {
onUpdate({resultsVisibility})
}

const handleUpdateHeight = (resultsPanelHeight: number): void => {
onUpdate({resultsPanelHeight})
}

// Ensure results renders with proper height on initial render
useEffect(() => {
updateResultsStyle()
}, [])

// Update results height when associated props change
useEffect(() => {
updateResultsStyle()
}, [size, visibility, resizingEnabled])

// Update local height when context height changes
// so long as it is a different value
useEffect(() => {
if (height !== size) {
updateSize(height)
}
}, [height])

// Handle changes in drag state
useEffect(() => {
if (isDragging === true) {
dragHandleRef.current &&
dragHandleRef.current.classList.add(
'notebook-raw-data--drag-handle__dragging'
)
}

if (isDragging === false) {
dragHandleRef.current &&
dragHandleRef.current.classList.remove(
'notebook-raw-data--drag-handle__dragging'
)
handleUpdateHeight(size)
}
}, [isDragging])

const handleMouseMove = (e: MouseEvent): void => {
if (!resultsBodyRef.current) {
return
}

const {pageY} = e
const {top} = resultsBodyRef.current.getBoundingClientRect()

const updatedHeight = Math.round(
Math.max(pageY - top, MINIMUM_RESULTS_PANEL_HEIGHT)
)

updateSize(updatedHeight)
}

const handleMouseDown = (): void => {
updateDragging(true)
const body = document.getElementsByTagName('body')[0]
body && body.classList.add('notebook-results--dragging')

window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('mouseup', handleMouseUp)
}

const handleMouseUp = (): void => {
updateDragging(false)
const body = document.getElementsByTagName('body')[0]
body && body.classList.remove('notebook-results--dragging')

window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('mouseup', handleMouseUp)
}

let resultsBody = children

if (!resizingEnabled) {
resultsBody = (
<div className="notebook-raw-data--empty">
Run the Flow to see results
</div>
)
}

if (resizingEnabled && visibility === 'hidden') {
resultsBody = <div className="notebook-raw-data--empty">Results hidden</div>
}

return (
<>
<ResizerHeader
resizingEnabled={resizingEnabled}
visibility={visibility}
onUpdateVisibility={handleUpdateVisibility}
onStartDrag={handleMouseDown}
dragHandleRef={dragHandleRef}
/>
<div className={resultsBodyClassName} ref={resultsBodyRef}>
{resultsBody}
</div>
</>
)
}

export default Resizer
66 changes: 66 additions & 0 deletions ui/src/notebooks/pipes/Query/ResizerHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Libraries
import React, {FC, RefObject} from 'react'
import classnames from 'classnames'

// Components
import {Icon, IconFont} from '@influxdata/clockface'

// Types
import {Visibility} from 'src/notebooks/pipes/Query'

interface Props {
visibility: Visibility
onUpdateVisibility: (visibility: Visibility) => void
onStartDrag: () => void
resizingEnabled: boolean
dragHandleRef: RefObject<HTMLDivElement>
}

const ResizerHeader: FC<Props> = ({
visibility,
onUpdateVisibility,
onStartDrag,
resizingEnabled,
dragHandleRef,
}) => {
const glyph = visibility === 'visible' ? IconFont.EyeOpen : IconFont.EyeClosed
const className = classnames('notebook-raw-data--header', {
[`notebook-raw-data--header__${visibility}`]: resizingEnabled && visibility,
})

if (!resizingEnabled) {
return (
<div className={className}>
<Icon glyph={IconFont.Zap} className="notebook-raw-data--vis-toggle" />
</div>
)
}

const handleToggleVisibility = (): void => {
if (visibility === 'visible') {
onUpdateVisibility('hidden')
} else {
onUpdateVisibility('visible')
}
}

return (
<div className={className}>
<div onClick={handleToggleVisibility}>
<Icon className="notebook-raw-data--vis-toggle" glyph={glyph} />
</div>
<div
className="notebook-raw-data--drag-handle"
onMouseDown={onStartDrag}
ref={dragHandleRef}
title="Drag to resize results table"
>
<div className="notebook-raw-data--drag-icon" />
<div className="notebook-raw-data--drag-icon" />
<div className="notebook-raw-data--drag-icon" />
</div>
</div>
)
}

export default ResizerHeader
37 changes: 9 additions & 28 deletions ui/src/notebooks/pipes/Query/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,26 @@
import React, {FC} from 'react'
import {BothResults} from 'src/notebooks/context/query'
import {AutoSizer} from 'react-virtualized'
import classnames from 'classnames'

// Components
import RawFluxDataTable from 'src/timeMachine/components/RawFluxDataTable'
import ResultsHeader from 'src/notebooks/pipes/Query/ResultsHeader'
import Resizer from 'src/notebooks/pipes/Query/Resizer'

// Types
import {RawDataSize} from 'src/notebooks/pipes/Query'
import {PipeData} from 'src/notebooks/index'

interface Props {
data: PipeData
results: BothResults
size: RawDataSize
onUpdateSize: (size: RawDataSize) => void
onUpdate: (data: any) => void
}

const Results: FC<Props> = ({results, size, onUpdateSize}) => {
const Results: FC<Props> = ({results, onUpdate, data}) => {
const resultsExist = !!results.raw
const className = classnames('notebook-raw-data', {
[`notebook-raw-data__${size}`]: resultsExist && size,
})

let resultsBody = (
<div className="notebook-raw-data--empty">Run the Flow to see results</div>
)

if (resultsExist) {
resultsBody = (
<div className="notebook-raw-data--body">
return (
<div className="notebook-raw-data">
<Resizer data={data} onUpdate={onUpdate} resizingEnabled={resultsExist}>
<AutoSizer>
{({width, height}) =>
width &&
Expand All @@ -42,18 +34,7 @@ const Results: FC<Props> = ({results, size, onUpdateSize}) => {
)
}
</AutoSizer>
</div>
)
}

return (
<div className={className}>
<ResultsHeader
resultsExist={resultsExist}
size={size}
onUpdateSize={onUpdateSize}
/>
{resultsBody}
</Resizer>
</div>
)
}
Expand Down
55 changes: 0 additions & 55 deletions ui/src/notebooks/pipes/Query/ResultsHeader.tsx

This file was deleted.

5 changes: 3 additions & 2 deletions ui/src/notebooks/pipes/Query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import {register} from 'src/notebooks'
import View from './view'
import './style.scss'

export type RawDataSize = 'small' | 'medium' | 'large'
export type Visibility = 'visible' | 'hidden'

register({
type: 'query',
component: View,
button: 'Custom Script',
initial: {
rawDataSize: 'small',
resultsVisibility: 'visible',
resultsPanelHeight: 200,
activeQuery: 0,
queries: [
{
Expand Down
Loading

0 comments on commit c6ad3cb

Please sign in to comment.