From fbdbd6c4db38a3465e5f8996175181080778478d Mon Sep 17 00:00:00 2001 From: alexpaxton Date: Mon, 29 Jun 2020 12:01:23 -0700 Subject: [PATCH] feat(flows): ability to insert cells inline (#18750) * feat: show previous result helper in script cells regardless of position * feat: enable adding a cell at a specific index * refactor: give markdown cells some default text * feat: introduce components for inline cell insertion * feat: replace static divider with inline insert divider * refactor: consolidate AddButtons & InsertCellMenu Keeping it DRY --- ui/src/notebooks/components/AddButtons.tsx | 23 +++- ui/src/notebooks/components/header/index.tsx | 2 +- .../components/panel/InsertCellButton.scss | 60 +++++++++ .../components/panel/InsertCellButton.tsx | 120 ++++++++++++++++++ .../components/panel/NotebookPanel.tsx | 16 ++- ui/src/notebooks/context/notebook.tsx | 18 ++- ui/src/notebooks/pipes/Query/index.ts | 3 +- ui/src/notebooks/pipes/markdown/index.ts | 2 +- ui/src/notebooks/style.scss | 12 -- 9 files changed, 223 insertions(+), 33 deletions(-) create mode 100644 ui/src/notebooks/components/panel/InsertCellButton.scss create mode 100644 ui/src/notebooks/components/panel/InsertCellButton.tsx diff --git a/ui/src/notebooks/components/AddButtons.tsx b/ui/src/notebooks/components/AddButtons.tsx index bd109daca72..347086f24e4 100644 --- a/ui/src/notebooks/components/AddButtons.tsx +++ b/ui/src/notebooks/components/AddButtons.tsx @@ -11,7 +11,13 @@ import {PIPE_DEFINITIONS} from 'src/notebooks' import {event} from 'src/notebooks/shared/event' import {isFlagEnabled} from 'src/shared/utils/featureFlag' -const AddButtons: FC = () => { +interface Props { + index?: number + onInsert?: () => void + eventName: string +} + +const AddButtons: FC = ({index, onInsert, eventName}) => { const {addPipe} = useContext(NotebookContext) const pipes = Object.entries(PIPE_DEFINITIONS) @@ -40,14 +46,19 @@ const AddButtons: FC = () => { data = data() } - event('Notebook Add Button Clicked', { + onInsert && onInsert() + + event(eventName, { type: def.type, }) - addPipe({ - ...data, - type, - }) + addPipe( + { + ...data, + type, + }, + index + ) }} color={ComponentColor.Secondary} /> diff --git a/ui/src/notebooks/components/header/index.tsx b/ui/src/notebooks/components/header/index.tsx index 443cc64f2d1..19aed1430a4 100644 --- a/ui/src/notebooks/components/header/index.tsx +++ b/ui/src/notebooks/components/header/index.tsx @@ -15,7 +15,7 @@ const Header: FC = () => (

Add Cell:

- +
diff --git a/ui/src/notebooks/components/panel/InsertCellButton.scss b/ui/src/notebooks/components/panel/InsertCellButton.scss new file mode 100644 index 00000000000..93ecb20537c --- /dev/null +++ b/ui/src/notebooks/components/panel/InsertCellButton.scss @@ -0,0 +1,60 @@ +@import '@influxdata/clockface/dist/variables.scss'; +@import '~src/notebooks/NotebookVariables.scss'; + +.notebook-divider { + height: 12px; + margin: $cf-marg-a $notebook-panel--gutter; + position: relative; + + &:after { + content: ''; + position: absolute; + top: 50%; + left: 0; + right: 0; + background-color: $notebook-divider-color; + height: $cf-border; + transform: translateY(-50%); + z-index: 0; + } + + &:hover { + cursor: pointer; + } + + &:last-of-type { + margin-bottom: 25vh; + } +} + +.notebook-divider--button { + position: absolute; + z-index: 1; + top: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; + opacity: 0; + cursor: pointer; + + &:after { + border-radius: 50%; + } + + .notebook-divider:hover &, + .notebook-divider__popped &, + .notebook-divider__popped:hover & { + opacity: 1; + } +} + +.insert-cell-menu { + padding: $cf-marg-c; + font-size: 14px; +} + +.insert-cell-menu--title { + font-weight: $cf-font-weight--medium; + margin-top: 0; + margin-bottom: $cf-marg-b !important; + user-select: none; +} diff --git a/ui/src/notebooks/components/panel/InsertCellButton.tsx b/ui/src/notebooks/components/panel/InsertCellButton.tsx new file mode 100644 index 00000000000..26b0ded3c54 --- /dev/null +++ b/ui/src/notebooks/components/panel/InsertCellButton.tsx @@ -0,0 +1,120 @@ +// Libraries +import React, {FC, useRef, useEffect} from 'react' + +// Components +import { + Popover, + Appearance, + ComponentColor, + ComponentSize, + SquareButton, + IconFont, + FlexBox, + FlexDirection, + AlignItems, +} from '@influxdata/clockface' +import AddButtons from 'src/notebooks/components/AddButtons' + +// Styles +import 'src/notebooks/components/panel/InsertCellButton.scss' + +interface Props { + index: number +} + +const InsertCellButton: FC = ({index}) => { + const dividerRef = useRef(null) + const buttonRef = useRef(null) + const popoverVisible = useRef(false) + const buttonPositioningEnabled = useRef(false) + + useEffect(() => { + window.addEventListener('mousemove', handleMouseMove) + + return () => { + window.removeEventListener('mousemove', handleMouseMove) + } + }, []) + + const handleMouseMove = (e: MouseEvent): void => { + if (!dividerRef.current || !buttonRef.current) { + return + } + + if ( + popoverVisible.current === false && + buttonPositioningEnabled.current === true + ) { + const {pageX} = e + const {left, width} = dividerRef.current.getBoundingClientRect() + + const minLeft = 0 + const maxLeft = width + + const buttonLeft = Math.min(Math.max(pageX - left, minLeft), maxLeft) + buttonRef.current.setAttribute('style', `left: ${buttonLeft}px`) + } + } + + const handleMouseEnter = () => { + buttonPositioningEnabled.current = true + } + + const handleMouseLeave = () => { + buttonPositioningEnabled.current = false + } + + const handlePopoverShow = () => { + popoverVisible.current = true + dividerRef.current && + dividerRef.current.classList.add('notebook-divider__popped') + } + + const handlePopoverHide = () => { + popoverVisible.current = false + dividerRef.current && + dividerRef.current.classList.remove('notebook-divider__popped') + } + + return ( +
+ + ( + +

Insert Cell Here

+ +
+ )} + /> +
+ ) +} + +export default InsertCellButton diff --git a/ui/src/notebooks/components/panel/NotebookPanel.tsx b/ui/src/notebooks/components/panel/NotebookPanel.tsx index ebf6b2839cc..bc2a71e4032 100644 --- a/ui/src/notebooks/components/panel/NotebookPanel.tsx +++ b/ui/src/notebooks/components/panel/NotebookPanel.tsx @@ -19,6 +19,7 @@ import { ClickOutside, } from '@influxdata/clockface' import RemovePanelButton from 'src/notebooks/components/panel/RemovePanelButton' +import InsertCellButton from 'src/notebooks/components/panel/InsertCellButton' import PanelVisibilityToggle from 'src/notebooks/components/panel/PanelVisibilityToggle' import MovePanelButton from 'src/notebooks/components/panel/MovePanelButton' import NotebookPanelTitle from 'src/notebooks/components/panel/NotebookPanelTitle' @@ -113,12 +114,15 @@ const NotebookPanel: FC = ({index, children, controls}) => { } return ( - -
- -
{children}
-
-
+ <> + +
+ +
{children}
+
+
+ + ) } diff --git a/ui/src/notebooks/context/notebook.tsx b/ui/src/notebooks/context/notebook.tsx index 7136157f9a6..aca0d6f07aa 100644 --- a/ui/src/notebooks/context/notebook.tsx +++ b/ui/src/notebooks/context/notebook.tsx @@ -17,7 +17,7 @@ export interface NotebookContextType { pipes: PipeData[] meta: PipeMeta[] // data only used for the view layer for Notebooks results: BothResults[] - addPipe: (pipe: PipeData) => void + addPipe: (pipe: PipeData, insertAtIndex?: number) => void updatePipe: (idx: number, pipe: PipeData) => void updateMeta: (idx: number, pipe: PipeMeta) => void updateResult: (idx: number, result: BothResults) => void @@ -84,14 +84,23 @@ export const NotebookProvider: FC = ({children}) => { const _setResults = useCallback(setResults, [id]) const addPipe = useCallback( - (pipe: PipeData) => { - const add = data => { + (pipe: PipeData, insertAtIndex?: number) => { + let add = data => { return pipes => { pipes.push(data) return pipes.slice() } } + if (insertAtIndex !== undefined) { + add = data => { + return pipes => { + pipes.splice(insertAtIndex + 1, 0, data) + return pipes.slice() + } + } + } + if (pipes.length && pipe.type !== 'query') { _setResults(add({...results[results.length - 1]})) _setMeta( @@ -123,9 +132,6 @@ export const NotebookProvider: FC = ({children}) => { queries: [ { ...pipe.queries[0], - text: - pipe.queries[0].text + - '\n\n// tip: use the __PREVIOUS_RESULT__ variable to use results from the previous cell', }, ], } diff --git a/ui/src/notebooks/pipes/Query/index.ts b/ui/src/notebooks/pipes/Query/index.ts index 3667f6cfaa6..41b04710c37 100644 --- a/ui/src/notebooks/pipes/Query/index.ts +++ b/ui/src/notebooks/pipes/Query/index.ts @@ -13,7 +13,8 @@ register({ activeQuery: 0, queries: [ { - text: '// Write Flux script here', + text: + '// Write Flux script here\n// use __PREVIOUS_RESULT__ to continue building from the previous cell', editMode: 'advanced', builderConfig: { buckets: [], diff --git a/ui/src/notebooks/pipes/markdown/index.ts b/ui/src/notebooks/pipes/markdown/index.ts index 496f2cb185f..15f8899c04c 100644 --- a/ui/src/notebooks/pipes/markdown/index.ts +++ b/ui/src/notebooks/pipes/markdown/index.ts @@ -170,7 +170,7 @@ register({ component: MarkdownPanel, button: 'Markdown', initial: () => ({ - text: TEST_MODE ? TESTCASE : '', + text: TEST_MODE ? TESTCASE : 'Type markdown here', mode: 'edit', }), }) diff --git a/ui/src/notebooks/style.scss b/ui/src/notebooks/style.scss index a77f002c1ce..c4b00b81416 100644 --- a/ui/src/notebooks/style.scss +++ b/ui/src/notebooks/style.scss @@ -35,18 +35,6 @@ align-items: stretch; border-radius: $cf-radius; margin: 0 $notebook-panel--gutter; - - &:after { - content: ''; - height: $cf-border; - background-color: $notebook-divider-color; - border-radius: $cf-border / 2; - margin: $cf-marg-a 0; - } - - &:last-of-type:after { - display: none; - } } .notebook-panel--header,