feat: keep notebook meta out of the pipes (#18163)

pull/18186/head^2
Alex Boatwright 2020-05-20 12:26:23 -07:00 committed by GitHub
parent d1275aaff7
commit 27fa70a4d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 177 additions and 173 deletions

View File

@ -1,18 +1,16 @@
import {FC, createElement, useContext} from 'react'
import {NotebookContext} from 'src/notebooks/context/notebook'
import {FC, createElement} from 'react'
import {PIPE_DEFINITIONS, PipeProp} from 'src/notebooks'
const Pipe: FC<PipeProp> = props => {
const {index} = props
const {pipes} = useContext(NotebookContext)
const {data} = props
if (!PIPE_DEFINITIONS.hasOwnProperty(pipes[index].type)) {
throw new Error(`Pipe type [${pipes[index].type}] not registered`)
if (!PIPE_DEFINITIONS.hasOwnProperty(data.type)) {
throw new Error(`Pipe type [${data.type}] not registered`)
return null
}
return createElement(PIPE_DEFINITIONS[pipes[index].type].component, props)
return createElement(PIPE_DEFINITIONS[data.type].component, props)
}
export default Pipe

View File

@ -1,21 +1,30 @@
import React, {FC, useContext} from 'react'
import React, {FC, useContext, createElement} from 'react'
import {PipeContextProps, PipeData} from 'src/notebooks'
import Pipe from 'src/notebooks/components/Pipe'
import {NotebookContext} from 'src/notebooks/context/notebook'
import NotebookPanel from 'src/notebooks/components/panel/NotebookPanel'
const PipeList: FC = () => {
const {id, pipes, removePipe, movePipe} = useContext(NotebookContext)
const _pipes = pipes.map((_, index) => {
const remove = () => removePipe(index)
const moveUp = () => movePipe(index, index - 1)
const moveDown = () => movePipe(index, index + 1)
const {id, pipes, updatePipe} = useContext(NotebookContext)
const _pipes = pipes.map((pipe, index) => {
const panel: FC<PipeContextProps> = props => {
const _props = {
...props,
index,
}
return createElement(NotebookPanel, _props)
}
const onUpdate = (data: PipeData) => {
updatePipe(index, data)
}
return (
<Pipe
index={index}
remove={remove}
moveUp={moveUp}
moveDown={moveDown}
key={`pipe-${id}-${index}`}
data={pipe}
onUpdate={onUpdate}
Context={panel}
/>
)
})

View File

@ -1,5 +1,5 @@
// Libraries
import React, {FC, ReactChildren, useState} from 'react'
import React, {FC, useContext} from 'react'
import classnames from 'classnames'
// Components
@ -9,43 +9,32 @@ import {
AlignItems,
JustifyContent,
} from '@influxdata/clockface'
import {PipeContextProps} from 'src/notebooks'
import {NotebookContext} from 'src/notebooks/context/notebook'
import RemovePanelButton from 'src/notebooks/components/panel/RemovePanelButton'
import PanelVisibilityToggle from 'src/notebooks/components/panel/PanelVisibilityToggle'
import MovePanelButton from 'src/notebooks/components/panel/MovePanelButton'
import NotebookPanelTitle from 'src/notebooks/components/panel/NotebookPanelTitle'
interface Props {
id: string
children: ReactChildren | JSX.Element | JSX.Element[]
title: string
onTitleChange?: (title: string) => void
previousPanelTitle?: string
controlsLeft?: JSX.Element | JSX.Element[]
controlsRight?: JSX.Element | JSX.Element[]
onRemove?: () => void
onMoveUp?: () => void
onMoveDown?: () => void
export interface Props extends PipeContextProps {
index: number
}
export type NotebookPanelVisibility = 'hidden' | 'visible'
const NotebookPanel: FC<Props> = ({index, children}) => {
const {pipes, removePipe, movePipe, meta} = useContext(NotebookContext)
const canBeMovedUp = index > 0
const canBeMovedDown = index < pipes.length - 1
const canBeRemoved = index !== 0
const NotebookPanel: FC<Props> = ({
children,
title,
previousPanelTitle,
onTitleChange,
controlsLeft,
controlsRight,
onRemove,
onMoveUp,
onMoveDown,
}) => {
const [panelVisibility, setPanelVisibility] = useState<
NotebookPanelVisibility
>('visible')
const moveUp = canBeMovedUp ? () => movePipe(index, index - 1) : null
const moveDown = canBeMovedDown ? () => movePipe(index, index + 1) : null
const remove = canBeRemoved ? () => removePipe(index) : null
const isVisible = meta[index].visible
const panelClassName = classnames('notebook-panel', {
[`notebook-panel__${panelVisibility}`]: panelVisibility,
[`notebook-panel__visible`]: isVisible,
[`notebook-panel__hidden`]: !isVisible,
})
return (
@ -57,12 +46,7 @@ const NotebookPanel: FC<Props> = ({
margin={ComponentSize.Small}
justifyContent={JustifyContent.FlexStart}
>
<NotebookPanelTitle
title={title}
onTitleChange={onTitleChange}
previousPanelTitle={previousPanelTitle}
/>
{controlsLeft}
<NotebookPanelTitle index={index} />
</FlexBox>
<FlexBox
className="notebook-panel--header-right"
@ -70,19 +54,13 @@ const NotebookPanel: FC<Props> = ({
margin={ComponentSize.Small}
justifyContent={JustifyContent.FlexEnd}
>
{controlsRight}
<MovePanelButton direction="up" onClick={onMoveUp} />
<MovePanelButton direction="down" onClick={onMoveDown} />
<PanelVisibilityToggle
onToggle={setPanelVisibility}
visibility={panelVisibility}
/>
<RemovePanelButton onRemove={onRemove} />
<MovePanelButton direction="up" onClick={moveUp} />
<MovePanelButton direction="down" onClick={moveDown} />
<PanelVisibilityToggle index={index} />
<RemovePanelButton onRemove={remove} />
</FlexBox>
</div>
<div className="notebook-panel--body">
{panelVisibility === 'visible' && children}
</div>
<div className="notebook-panel--body">{isVisible && children}</div>
</div>
)
}

View File

@ -1,55 +1,41 @@
// Libraries
import React, {FC, ChangeEvent} from 'react'
// Components
import {Icon, IconFont} from '@influxdata/clockface'
import React, {FC, ChangeEvent, useContext} from 'react'
import {NotebookContext, PipeMeta} from 'src/notebooks/context/notebook'
interface Props {
title: string
onTitleChange?: (title: string) => void
previousPanelTitle?: string
index: number
}
const NotebookPanelTitle: FC<Props> = ({
title,
onTitleChange,
previousPanelTitle,
}) => {
const NotebookPanelTitle: FC<Props> = ({index}) => {
const {meta, updateMeta} = useContext(NotebookContext)
const title = meta[index].title
const onTitleChange = (value: string) => {
updateMeta(index, {
title: value,
} as PipeMeta)
}
let sourceName
let titleElement = <div className="notebook-panel--title">{title}</div>
if (previousPanelTitle) {
sourceName = (
<div className="notebook-panel--data-source">
{previousPanelTitle}
<Icon
glyph={IconFont.CaretRight}
className="notebook-panel--data-caret"
/>
</div>
)
const onChange = (e: ChangeEvent<HTMLInputElement>): void => {
const trimmedValue = e.target.value.replace(' ', '_')
onTitleChange(trimmedValue)
}
if (onTitleChange) {
const onChange = (e: ChangeEvent<HTMLInputElement>): void => {
const trimmedValue = e.target.value.replace(' ', '_')
onTitleChange(trimmedValue)
}
titleElement = (
<input
type="text"
value={title}
onChange={onChange}
placeholder="Enter an ID"
className="notebook-panel--editable-title"
autoComplete="off"
autoCorrect="off"
spellCheck={false}
maxLength={30}
/>
)
}
titleElement = (
<input
type="text"
value={title}
onChange={onChange}
placeholder="Enter an ID"
className="notebook-panel--editable-title"
autoComplete="off"
autoCorrect="off"
spellCheck={false}
maxLength={30}
/>
)
return (
<>

View File

@ -1,26 +1,23 @@
// Libraries
import React, {FC} from 'react'
import React, {FC, useContext} from 'react'
// Components
import {SquareButton, IconFont} from '@influxdata/clockface'
import {NotebookContext, PipeMeta} from 'src/notebooks/context/notebook'
// Types
import {NotebookPanelVisibility} from 'src/notebooks/components/panel/NotebookPanel'
interface Props {
visibility: NotebookPanelVisibility
onToggle: (visibility: NotebookPanelVisibility) => void
export interface Props {
index: number
}
const PanelVisibilityToggle: FC<Props> = ({visibility, onToggle}) => {
const icon = visibility === 'visible' ? IconFont.EyeOpen : IconFont.EyeClosed
const PanelVisibilityToggle: FC<Props> = ({index}) => {
const {meta, updateMeta} = useContext(NotebookContext)
const icon = meta[index].visible ? IconFont.EyeOpen : IconFont.EyeClosed
const handleClick = (): void => {
if (visibility === 'hidden') {
onToggle('visible')
} else if (visibility === 'visible') {
onToggle('hidden')
}
updateMeta(index, {
visible: !meta[index].visible,
} as PipeMeta)
}
return <SquareButton icon={icon} onClick={handleClick} />

View File

@ -1,14 +1,18 @@
import React, {FC, useState} from 'react'
import {PipeData} from 'src/notebooks'
// TODO make this polymorphic to mimic the self registration
// of pipe stages
export type Pipe = any
export interface PipeMeta {
title: string
visible: boolean
}
export interface NotebookContextType {
id: string
pipes: Pipe[]
addPipe: (pipe: Pipe) => void
updatePipe: (idx: number, pipe: Pipe) => void
pipes: PipeData[]
meta: PipeMeta[] // data only used for the view layer for Notebooks
addPipe: (pipe: PipeData) => void
updatePipe: (idx: number, pipe: PipeData) => void
updateMeta: (idx: number, pipe: PipeMeta) => void
movePipe: (currentIdx: number, newIdx: number) => void
removePipe: (idx: number) => void
}
@ -16,8 +20,10 @@ export interface NotebookContextType {
export const DEFAULT_CONTEXT: NotebookContextType = {
id: 'new',
pipes: [],
meta: [],
addPipe: () => {},
updatePipe: () => {},
updateMeta: () => {},
movePipe: () => {},
removePipe: () => {},
}
@ -36,18 +42,30 @@ export const NotebookContext = React.createContext<NotebookContextType>(
DEFAULT_CONTEXT
)
let GENERATOR_INDEX = 0
export const NotebookProvider: FC = ({children}) => {
const [id] = useState(DEFAULT_CONTEXT.id)
const [pipes, setPipes] = useState(DEFAULT_CONTEXT.pipes)
const [meta, setMeta] = useState(DEFAULT_CONTEXT.meta)
function addPipe(pipe: Pipe) {
setPipes(pipes => {
pipes.push(pipe)
return pipes.slice()
})
function addPipe(pipe: PipeData) {
const add = data => {
return pipes => {
pipes.push(data)
return pipes.slice()
}
}
setPipes(add(pipe))
setMeta(
add({
title: `Notebook_${++GENERATOR_INDEX}`,
visible: true,
})
)
}
function updatePipe(idx: number, pipe: Pipe) {
function updatePipe(idx: number, pipe: PipeData) {
setPipes(pipes => {
pipes[idx] = {
...pipes[idx],
@ -57,27 +75,41 @@ export const NotebookProvider: FC = ({children}) => {
})
}
function movePipe(currentIdx: number, newIdx: number) {
setPipes(pipes => {
const idx = ((newIdx % pipes.length) + pipes.length) % pipes.length
if (idx === currentIdx) {
return pipes
function updateMeta(idx: number, pipe: PipeMeta) {
setMeta(pipes => {
pipes[idx] = {
...pipes[idx],
...pipe,
}
const pipe = pipes.splice(currentIdx, 1)
pipes.splice(idx, 0, pipe[0])
return pipes.slice()
})
}
function movePipe(currentIdx: number, newIdx: number) {
const move = list => {
const idx = ((newIdx % list.length) + list.length) % list.length
if (idx === currentIdx) {
return list
}
const pipe = list.splice(currentIdx, 1)
list.splice(idx, 0, pipe[0])
return list.slice()
}
setPipes(move)
setMeta(move)
}
function removePipe(idx: number) {
setPipes(pipes => {
const remove = pipes => {
pipes.splice(idx, 1)
return pipes.slice()
})
}
setPipes(remove)
setMeta(remove)
}
return (
@ -85,7 +117,9 @@ export const NotebookProvider: FC = ({children}) => {
value={{
id,
pipes,
meta,
updatePipe,
updateMeta,
movePipe,
addPipe,
removePipe,

View File

@ -1,10 +1,17 @@
import {FunctionComponent, ComponentClass} from 'react'
import {FunctionComponent, ComponentClass, ReactNode} from 'react'
export interface PipeContextProps {
children?: ReactNode
}
export type PipeData = any
export interface PipeProp {
index: number
remove: () => void
moveUp: () => void
moveDown: () => void
data: PipeData
onUpdate: (data: PipeData) => void
Context:
| FunctionComponent<PipeContextProps>
| ComponentClass<PipeContextProps>
}
// NOTE: keep this interface as small as possible and

View File

@ -1,5 +1,18 @@
.notebook-example {
.pipe-example {
.notebook-panel--header {
transition: opacity 0.2s;
opacity: 0;
}
&:hover {
.notebook-panel--header {
opacity: 1;
}
}
h1 {
color: #f00;
margin: 0;
padding: 1em 0;
}
}

View File

@ -1,29 +1,11 @@
import React, {FC, useContext} from 'react'
import React, {FC} from 'react'
import {PipeProp} from 'src/notebooks'
import {NotebookContext} from 'src/notebooks/context/notebook'
import {NotebookPanel} from 'src/notebooks/components/Notebook'
const TITLE = 'Example Pipe'
const ExampleView: FC<PipeProp> = ({index, remove, moveUp, moveDown}) => {
const {pipes} = useContext(NotebookContext)
const pipe = pipes[index]
const canBeMovedUp = index > 0
const canBeMovedDown = index < pipes.length - 1
const canBeRemoved = index !== 0
const ExampleView: FC<PipeProp> = ({data, Context}) => {
return (
<NotebookPanel
id={`pipe${index}`}
onMoveUp={canBeMovedUp && moveUp}
onMoveDown={canBeMovedDown && moveDown}
onRemove={canBeRemoved && remove}
title={TITLE}
>
<h1>{pipe.text}</h1>
</NotebookPanel>
<Context>
<h1>{data.text}</h1>
</Context>
)
}