feat: keep notebook meta out of the pipes (#18163)
parent
d1275aaff7
commit
27fa70a4d7
|
@ -1,18 +1,16 @@
|
||||||
import {FC, createElement, useContext} from 'react'
|
import {FC, createElement} from 'react'
|
||||||
import {NotebookContext} from 'src/notebooks/context/notebook'
|
|
||||||
|
|
||||||
import {PIPE_DEFINITIONS, PipeProp} from 'src/notebooks'
|
import {PIPE_DEFINITIONS, PipeProp} from 'src/notebooks'
|
||||||
|
|
||||||
const Pipe: FC<PipeProp> = props => {
|
const Pipe: FC<PipeProp> = props => {
|
||||||
const {index} = props
|
const {data} = props
|
||||||
const {pipes} = useContext(NotebookContext)
|
|
||||||
|
|
||||||
if (!PIPE_DEFINITIONS.hasOwnProperty(pipes[index].type)) {
|
if (!PIPE_DEFINITIONS.hasOwnProperty(data.type)) {
|
||||||
throw new Error(`Pipe type [${pipes[index].type}] not registered`)
|
throw new Error(`Pipe type [${data.type}] not registered`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return createElement(PIPE_DEFINITIONS[pipes[index].type].component, props)
|
return createElement(PIPE_DEFINITIONS[data.type].component, props)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Pipe
|
export default Pipe
|
||||||
|
|
|
@ -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 Pipe from 'src/notebooks/components/Pipe'
|
||||||
import {NotebookContext} from 'src/notebooks/context/notebook'
|
import {NotebookContext} from 'src/notebooks/context/notebook'
|
||||||
|
import NotebookPanel from 'src/notebooks/components/panel/NotebookPanel'
|
||||||
|
|
||||||
const PipeList: FC = () => {
|
const PipeList: FC = () => {
|
||||||
const {id, pipes, removePipe, movePipe} = useContext(NotebookContext)
|
const {id, pipes, updatePipe} = useContext(NotebookContext)
|
||||||
const _pipes = pipes.map((_, index) => {
|
const _pipes = pipes.map((pipe, index) => {
|
||||||
const remove = () => removePipe(index)
|
const panel: FC<PipeContextProps> = props => {
|
||||||
const moveUp = () => movePipe(index, index - 1)
|
const _props = {
|
||||||
const moveDown = () => movePipe(index, index + 1)
|
...props,
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
|
||||||
|
return createElement(NotebookPanel, _props)
|
||||||
|
}
|
||||||
|
const onUpdate = (data: PipeData) => {
|
||||||
|
updatePipe(index, data)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pipe
|
<Pipe
|
||||||
index={index}
|
|
||||||
remove={remove}
|
|
||||||
moveUp={moveUp}
|
|
||||||
moveDown={moveDown}
|
|
||||||
key={`pipe-${id}-${index}`}
|
key={`pipe-${id}-${index}`}
|
||||||
|
data={pipe}
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
Context={panel}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Libraries
|
// Libraries
|
||||||
import React, {FC, ReactChildren, useState} from 'react'
|
import React, {FC, useContext} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
@ -9,43 +9,32 @@ import {
|
||||||
AlignItems,
|
AlignItems,
|
||||||
JustifyContent,
|
JustifyContent,
|
||||||
} from '@influxdata/clockface'
|
} from '@influxdata/clockface'
|
||||||
|
import {PipeContextProps} from 'src/notebooks'
|
||||||
|
import {NotebookContext} from 'src/notebooks/context/notebook'
|
||||||
import RemovePanelButton from 'src/notebooks/components/panel/RemovePanelButton'
|
import RemovePanelButton from 'src/notebooks/components/panel/RemovePanelButton'
|
||||||
import PanelVisibilityToggle from 'src/notebooks/components/panel/PanelVisibilityToggle'
|
import PanelVisibilityToggle from 'src/notebooks/components/panel/PanelVisibilityToggle'
|
||||||
import MovePanelButton from 'src/notebooks/components/panel/MovePanelButton'
|
import MovePanelButton from 'src/notebooks/components/panel/MovePanelButton'
|
||||||
import NotebookPanelTitle from 'src/notebooks/components/panel/NotebookPanelTitle'
|
import NotebookPanelTitle from 'src/notebooks/components/panel/NotebookPanelTitle'
|
||||||
|
|
||||||
interface Props {
|
export interface Props extends PipeContextProps {
|
||||||
id: string
|
index: number
|
||||||
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 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> = ({
|
const moveUp = canBeMovedUp ? () => movePipe(index, index - 1) : null
|
||||||
children,
|
const moveDown = canBeMovedDown ? () => movePipe(index, index + 1) : null
|
||||||
title,
|
const remove = canBeRemoved ? () => removePipe(index) : null
|
||||||
previousPanelTitle,
|
|
||||||
onTitleChange,
|
const isVisible = meta[index].visible
|
||||||
controlsLeft,
|
|
||||||
controlsRight,
|
|
||||||
onRemove,
|
|
||||||
onMoveUp,
|
|
||||||
onMoveDown,
|
|
||||||
}) => {
|
|
||||||
const [panelVisibility, setPanelVisibility] = useState<
|
|
||||||
NotebookPanelVisibility
|
|
||||||
>('visible')
|
|
||||||
|
|
||||||
const panelClassName = classnames('notebook-panel', {
|
const panelClassName = classnames('notebook-panel', {
|
||||||
[`notebook-panel__${panelVisibility}`]: panelVisibility,
|
[`notebook-panel__visible`]: isVisible,
|
||||||
|
[`notebook-panel__hidden`]: !isVisible,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -57,12 +46,7 @@ const NotebookPanel: FC<Props> = ({
|
||||||
margin={ComponentSize.Small}
|
margin={ComponentSize.Small}
|
||||||
justifyContent={JustifyContent.FlexStart}
|
justifyContent={JustifyContent.FlexStart}
|
||||||
>
|
>
|
||||||
<NotebookPanelTitle
|
<NotebookPanelTitle index={index} />
|
||||||
title={title}
|
|
||||||
onTitleChange={onTitleChange}
|
|
||||||
previousPanelTitle={previousPanelTitle}
|
|
||||||
/>
|
|
||||||
{controlsLeft}
|
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
<FlexBox
|
<FlexBox
|
||||||
className="notebook-panel--header-right"
|
className="notebook-panel--header-right"
|
||||||
|
@ -70,19 +54,13 @@ const NotebookPanel: FC<Props> = ({
|
||||||
margin={ComponentSize.Small}
|
margin={ComponentSize.Small}
|
||||||
justifyContent={JustifyContent.FlexEnd}
|
justifyContent={JustifyContent.FlexEnd}
|
||||||
>
|
>
|
||||||
{controlsRight}
|
<MovePanelButton direction="up" onClick={moveUp} />
|
||||||
<MovePanelButton direction="up" onClick={onMoveUp} />
|
<MovePanelButton direction="down" onClick={moveDown} />
|
||||||
<MovePanelButton direction="down" onClick={onMoveDown} />
|
<PanelVisibilityToggle index={index} />
|
||||||
<PanelVisibilityToggle
|
<RemovePanelButton onRemove={remove} />
|
||||||
onToggle={setPanelVisibility}
|
|
||||||
visibility={panelVisibility}
|
|
||||||
/>
|
|
||||||
<RemovePanelButton onRemove={onRemove} />
|
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</div>
|
</div>
|
||||||
<div className="notebook-panel--body">
|
<div className="notebook-panel--body">{isVisible && children}</div>
|
||||||
{panelVisibility === 'visible' && children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,23 @@
|
||||||
// Libraries
|
// Libraries
|
||||||
import React, {FC, ChangeEvent} from 'react'
|
import React, {FC, ChangeEvent, useContext} from 'react'
|
||||||
|
import {NotebookContext, PipeMeta} from 'src/notebooks/context/notebook'
|
||||||
// Components
|
|
||||||
import {Icon, IconFont} from '@influxdata/clockface'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string
|
index: number
|
||||||
onTitleChange?: (title: string) => void
|
|
||||||
previousPanelTitle?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NotebookPanelTitle: FC<Props> = ({
|
const NotebookPanelTitle: FC<Props> = ({index}) => {
|
||||||
title,
|
const {meta, updateMeta} = useContext(NotebookContext)
|
||||||
onTitleChange,
|
const title = meta[index].title
|
||||||
previousPanelTitle,
|
const onTitleChange = (value: string) => {
|
||||||
}) => {
|
updateMeta(index, {
|
||||||
|
title: value,
|
||||||
|
} as PipeMeta)
|
||||||
|
}
|
||||||
|
|
||||||
let sourceName
|
let sourceName
|
||||||
let titleElement = <div className="notebook-panel--title">{title}</div>
|
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onTitleChange) {
|
|
||||||
const onChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
const onChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
const trimmedValue = e.target.value.replace(' ', '_')
|
const trimmedValue = e.target.value.replace(' ', '_')
|
||||||
onTitleChange(trimmedValue)
|
onTitleChange(trimmedValue)
|
||||||
|
@ -49,7 +36,6 @@ const NotebookPanelTitle: FC<Props> = ({
|
||||||
maxLength={30}
|
maxLength={30}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
// Libraries
|
// Libraries
|
||||||
import React, {FC} from 'react'
|
import React, {FC, useContext} from 'react'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import {SquareButton, IconFont} from '@influxdata/clockface'
|
import {SquareButton, IconFont} from '@influxdata/clockface'
|
||||||
|
import {NotebookContext, PipeMeta} from 'src/notebooks/context/notebook'
|
||||||
|
|
||||||
// Types
|
export interface Props {
|
||||||
import {NotebookPanelVisibility} from 'src/notebooks/components/panel/NotebookPanel'
|
index: number
|
||||||
|
|
||||||
interface Props {
|
|
||||||
visibility: NotebookPanelVisibility
|
|
||||||
onToggle: (visibility: NotebookPanelVisibility) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PanelVisibilityToggle: FC<Props> = ({visibility, onToggle}) => {
|
const PanelVisibilityToggle: FC<Props> = ({index}) => {
|
||||||
const icon = visibility === 'visible' ? IconFont.EyeOpen : IconFont.EyeClosed
|
const {meta, updateMeta} = useContext(NotebookContext)
|
||||||
|
|
||||||
|
const icon = meta[index].visible ? IconFont.EyeOpen : IconFont.EyeClosed
|
||||||
|
|
||||||
const handleClick = (): void => {
|
const handleClick = (): void => {
|
||||||
if (visibility === 'hidden') {
|
updateMeta(index, {
|
||||||
onToggle('visible')
|
visible: !meta[index].visible,
|
||||||
} else if (visibility === 'visible') {
|
} as PipeMeta)
|
||||||
onToggle('hidden')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SquareButton icon={icon} onClick={handleClick} />
|
return <SquareButton icon={icon} onClick={handleClick} />
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import React, {FC, useState} from 'react'
|
import React, {FC, useState} from 'react'
|
||||||
|
import {PipeData} from 'src/notebooks'
|
||||||
|
|
||||||
// TODO make this polymorphic to mimic the self registration
|
export interface PipeMeta {
|
||||||
// of pipe stages
|
title: string
|
||||||
export type Pipe = any
|
visible: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface NotebookContextType {
|
export interface NotebookContextType {
|
||||||
id: string
|
id: string
|
||||||
pipes: Pipe[]
|
pipes: PipeData[]
|
||||||
addPipe: (pipe: Pipe) => void
|
meta: PipeMeta[] // data only used for the view layer for Notebooks
|
||||||
updatePipe: (idx: number, pipe: Pipe) => void
|
addPipe: (pipe: PipeData) => void
|
||||||
|
updatePipe: (idx: number, pipe: PipeData) => void
|
||||||
|
updateMeta: (idx: number, pipe: PipeMeta) => void
|
||||||
movePipe: (currentIdx: number, newIdx: number) => void
|
movePipe: (currentIdx: number, newIdx: number) => void
|
||||||
removePipe: (idx: number) => void
|
removePipe: (idx: number) => void
|
||||||
}
|
}
|
||||||
|
@ -16,8 +20,10 @@ export interface NotebookContextType {
|
||||||
export const DEFAULT_CONTEXT: NotebookContextType = {
|
export const DEFAULT_CONTEXT: NotebookContextType = {
|
||||||
id: 'new',
|
id: 'new',
|
||||||
pipes: [],
|
pipes: [],
|
||||||
|
meta: [],
|
||||||
addPipe: () => {},
|
addPipe: () => {},
|
||||||
updatePipe: () => {},
|
updatePipe: () => {},
|
||||||
|
updateMeta: () => {},
|
||||||
movePipe: () => {},
|
movePipe: () => {},
|
||||||
removePipe: () => {},
|
removePipe: () => {},
|
||||||
}
|
}
|
||||||
|
@ -36,18 +42,30 @@ export const NotebookContext = React.createContext<NotebookContextType>(
|
||||||
DEFAULT_CONTEXT
|
DEFAULT_CONTEXT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let GENERATOR_INDEX = 0
|
||||||
|
|
||||||
export const NotebookProvider: FC = ({children}) => {
|
export const NotebookProvider: FC = ({children}) => {
|
||||||
const [id] = useState(DEFAULT_CONTEXT.id)
|
const [id] = useState(DEFAULT_CONTEXT.id)
|
||||||
const [pipes, setPipes] = useState(DEFAULT_CONTEXT.pipes)
|
const [pipes, setPipes] = useState(DEFAULT_CONTEXT.pipes)
|
||||||
|
const [meta, setMeta] = useState(DEFAULT_CONTEXT.meta)
|
||||||
|
|
||||||
function addPipe(pipe: Pipe) {
|
function addPipe(pipe: PipeData) {
|
||||||
setPipes(pipes => {
|
const add = data => {
|
||||||
pipes.push(pipe)
|
return pipes => {
|
||||||
|
pipes.push(data)
|
||||||
return pipes.slice()
|
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 => {
|
setPipes(pipes => {
|
||||||
pipes[idx] = {
|
pipes[idx] = {
|
||||||
...pipes[idx],
|
...pipes[idx],
|
||||||
|
@ -57,27 +75,41 @@ export const NotebookProvider: FC = ({children}) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function movePipe(currentIdx: number, newIdx: number) {
|
function updateMeta(idx: number, pipe: PipeMeta) {
|
||||||
setPipes(pipes => {
|
setMeta(pipes => {
|
||||||
const idx = ((newIdx % pipes.length) + pipes.length) % pipes.length
|
pipes[idx] = {
|
||||||
|
...pipes[idx],
|
||||||
if (idx === currentIdx) {
|
...pipe,
|
||||||
return pipes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pipe = pipes.splice(currentIdx, 1)
|
|
||||||
|
|
||||||
pipes.splice(idx, 0, pipe[0])
|
|
||||||
|
|
||||||
return pipes.slice()
|
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) {
|
function removePipe(idx: number) {
|
||||||
setPipes(pipes => {
|
const remove = pipes => {
|
||||||
pipes.splice(idx, 1)
|
pipes.splice(idx, 1)
|
||||||
return pipes.slice()
|
return pipes.slice()
|
||||||
})
|
}
|
||||||
|
setPipes(remove)
|
||||||
|
setMeta(remove)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -85,7 +117,9 @@ export const NotebookProvider: FC = ({children}) => {
|
||||||
value={{
|
value={{
|
||||||
id,
|
id,
|
||||||
pipes,
|
pipes,
|
||||||
|
meta,
|
||||||
updatePipe,
|
updatePipe,
|
||||||
|
updateMeta,
|
||||||
movePipe,
|
movePipe,
|
||||||
addPipe,
|
addPipe,
|
||||||
removePipe,
|
removePipe,
|
||||||
|
|
|
@ -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 {
|
export interface PipeProp {
|
||||||
index: number
|
data: PipeData
|
||||||
remove: () => void
|
onUpdate: (data: PipeData) => void
|
||||||
moveUp: () => void
|
Context:
|
||||||
moveDown: () => void
|
| FunctionComponent<PipeContextProps>
|
||||||
|
| ComponentClass<PipeContextProps>
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: keep this interface as small as possible and
|
// NOTE: keep this interface as small as possible and
|
||||||
|
|
|
@ -1,5 +1,18 @@
|
||||||
.notebook-example {
|
.pipe-example {
|
||||||
|
.notebook-panel--header {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.notebook-panel--header {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: #f00;
|
margin: 0;
|
||||||
|
padding: 1em 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,11 @@
|
||||||
import React, {FC, useContext} from 'react'
|
import React, {FC} from 'react'
|
||||||
import {PipeProp} from 'src/notebooks'
|
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 (
|
return (
|
||||||
<NotebookPanel
|
<Context>
|
||||||
id={`pipe${index}`}
|
<h1>{data.text}</h1>
|
||||||
onMoveUp={canBeMovedUp && moveUp}
|
</Context>
|
||||||
onMoveDown={canBeMovedDown && moveDown}
|
|
||||||
onRemove={canBeRemoved && remove}
|
|
||||||
title={TITLE}
|
|
||||||
>
|
|
||||||
<h1>{pipe.text}</h1>
|
|
||||||
</NotebookPanel>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue