diff --git a/ui/src/notebooks/components/Pipe.tsx b/ui/src/notebooks/components/Pipe.tsx index bcd18bc132..c51906f136 100644 --- a/ui/src/notebooks/components/Pipe.tsx +++ b/ui/src/notebooks/components/Pipe.tsx @@ -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 = 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 diff --git a/ui/src/notebooks/components/PipeList.tsx b/ui/src/notebooks/components/PipeList.tsx index 27ef2b3fff..9110bf4f92 100644 --- a/ui/src/notebooks/components/PipeList.tsx +++ b/ui/src/notebooks/components/PipeList.tsx @@ -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 = props => { + const _props = { + ...props, + index, + } + + return createElement(NotebookPanel, _props) + } + const onUpdate = (data: PipeData) => { + updatePipe(index, data) + } return ( ) }) diff --git a/ui/src/notebooks/components/panel/NotebookPanel.tsx b/ui/src/notebooks/components/panel/NotebookPanel.tsx index 5f73b1497f..7c24edd2a5 100644 --- a/ui/src/notebooks/components/panel/NotebookPanel.tsx +++ b/ui/src/notebooks/components/panel/NotebookPanel.tsx @@ -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 = ({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 = ({ - 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 = ({ margin={ComponentSize.Small} justifyContent={JustifyContent.FlexStart} > - - {controlsLeft} + = ({ margin={ComponentSize.Small} justifyContent={JustifyContent.FlexEnd} > - {controlsRight} - - - - + + + + -
- {panelVisibility === 'visible' && children} -
+
{isVisible && children}
) } diff --git a/ui/src/notebooks/components/panel/NotebookPanelTitle.tsx b/ui/src/notebooks/components/panel/NotebookPanelTitle.tsx index aaa80a2b07..7d11015400 100644 --- a/ui/src/notebooks/components/panel/NotebookPanelTitle.tsx +++ b/ui/src/notebooks/components/panel/NotebookPanelTitle.tsx @@ -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 = ({ - title, - onTitleChange, - previousPanelTitle, -}) => { +const NotebookPanelTitle: FC = ({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 =
{title}
- if (previousPanelTitle) { - sourceName = ( -
- {previousPanelTitle} - -
- ) + const onChange = (e: ChangeEvent): void => { + const trimmedValue = e.target.value.replace(' ', '_') + onTitleChange(trimmedValue) } - if (onTitleChange) { - const onChange = (e: ChangeEvent): void => { - const trimmedValue = e.target.value.replace(' ', '_') - onTitleChange(trimmedValue) - } - - titleElement = ( - - ) - } + titleElement = ( + + ) return ( <> diff --git a/ui/src/notebooks/components/panel/PanelVisibilityToggle.tsx b/ui/src/notebooks/components/panel/PanelVisibilityToggle.tsx index 183bdefc56..891ea2cf37 100644 --- a/ui/src/notebooks/components/panel/PanelVisibilityToggle.tsx +++ b/ui/src/notebooks/components/panel/PanelVisibilityToggle.tsx @@ -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 = ({visibility, onToggle}) => { - const icon = visibility === 'visible' ? IconFont.EyeOpen : IconFont.EyeClosed +const PanelVisibilityToggle: FC = ({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 diff --git a/ui/src/notebooks/context/notebook.tsx b/ui/src/notebooks/context/notebook.tsx index ecbcd2a73b..a94e8b3b4c 100644 --- a/ui/src/notebooks/context/notebook.tsx +++ b/ui/src/notebooks/context/notebook.tsx @@ -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( 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, diff --git a/ui/src/notebooks/index.ts b/ui/src/notebooks/index.ts index 045518e0a9..bd9f6b42f3 100644 --- a/ui/src/notebooks/index.ts +++ b/ui/src/notebooks/index.ts @@ -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 + | ComponentClass } // NOTE: keep this interface as small as possible and diff --git a/ui/src/notebooks/pipes/Example/style.scss b/ui/src/notebooks/pipes/Example/style.scss index f7b1753c94..26d0ab8865 100644 --- a/ui/src/notebooks/pipes/Example/style.scss +++ b/ui/src/notebooks/pipes/Example/style.scss @@ -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; } } diff --git a/ui/src/notebooks/pipes/Example/view.tsx b/ui/src/notebooks/pipes/Example/view.tsx index 43fb1dfc5c..f58e612b76 100644 --- a/ui/src/notebooks/pipes/Example/view.tsx +++ b/ui/src/notebooks/pipes/Example/view.tsx @@ -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 = ({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 = ({data, Context}) => { return ( - -

{pipe.text}

-
+ +

{data.text}

+
) }