diff --git a/frontend/src/lib/lemon-ui/LemonWidget/LemonWidget.tsx b/frontend/src/lib/lemon-ui/LemonWidget/LemonWidget.tsx index 36ef211c3109a..8a02b8ac7ba2b 100644 --- a/frontend/src/lib/lemon-ui/LemonWidget/LemonWidget.tsx +++ b/frontend/src/lib/lemon-ui/LemonWidget/LemonWidget.tsx @@ -8,10 +8,11 @@ export interface LemonWidgetProps { title: string collapsible?: boolean onClose?: () => void + actions?: React.ReactNode children: React.ReactChild } -export function LemonWidget({ title, collapsible = true, onClose, children }: LemonWidgetProps): JSX.Element { +export function LemonWidget({ title, collapsible = true, onClose, actions, children }: LemonWidgetProps): JSX.Element { const [isExpanded, setIsExpanded] = useState(true) return ( @@ -36,6 +37,8 @@ export function LemonWidget({ title, collapsible = true, onClose, children }: Le ) : ( {title} )} + {actions} + {onClose && } />} {isExpanded && {children}} diff --git a/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx b/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx index 6d90866fe208e..0369639282eb3 100644 --- a/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx @@ -67,8 +67,8 @@ export function NodeWrapper({ widgets = [], }: NodeWrapperProps & NotebookNodeViewProps): JSX.Element { const mountedNotebookLogic = useMountedLogic(notebookLogic) - const { isEditable, isShowingSidebar } = useValues(mountedNotebookLogic) - const { setIsShowingSidebar } = useActions(mountedNotebookLogic) + const { isEditable, editingNodeId } = useValues(mountedNotebookLogic) + const { setEditingNodeId } = useActions(mountedNotebookLogic) // nodeId can start null, but should then immediately be generated const nodeId = attributes.nodeId @@ -169,10 +169,12 @@ export function NodeWrapper({ {widgets.length > 0 ? ( setIsShowingSidebar(!isShowingSidebar)} + onClick={() => + setEditingNodeId(editingNodeId === nodeId ? null : nodeId) + } size="small" icon={} - active={isShowingSidebar && selected} + active={editingNodeId === nodeId} /> ) : null} diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodeQuery.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodeQuery.tsx index f692d37023970..195b3828f7de5 100644 --- a/frontend/src/scenes/notebooks/Nodes/NotebookNodeQuery.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodeQuery.tsx @@ -197,7 +197,6 @@ export const NotebookNodeQuery = createPostHogWidgetNode([ setPreviousNode: (node: Node | null) => ({ node }), setNextNode: (node: Node | null) => ({ node }), deleteNode: true, + selectNode: true, }), connect((props: NotebookNodeLogicProps) => ({ @@ -138,6 +139,18 @@ export const notebookNodeLogic = kea([ deleteNode: () => { const logic = values.notebookLogic logic.values.editor?.deleteRange({ from: props.getPos(), to: props.getPos() + props.node.nodeSize }).run() + if (values.notebookLogic.values.editingNodeId === props.nodeId) { + values.notebookLogic.actions.setEditingNodeId(null) + } + }, + + selectNode: () => { + const editor = values.notebookLogic.values.editor + + if (editor) { + editor.setSelection(props.getPos()) + editor.scrollToSelection() + } }, insertAfterLastNodeOfType: ({ nodeType, content }) => { diff --git a/frontend/src/scenes/notebooks/Notebook/Notebook.scss b/frontend/src/scenes/notebooks/Notebook/Notebook.scss index 059b06a20d10a..06ff7f8108336 100644 --- a/frontend/src/scenes/notebooks/Notebook/Notebook.scss +++ b/frontend/src/scenes/notebooks/Notebook/Notebook.scss @@ -127,13 +127,22 @@ width: 0px; transition: width var(--notebook-popover-transition-properties); + .NotebookSidebar__padding { + height: 3.5rem; + } + .NotebookSidebar__content { position: sticky; align-self: flex-start; - top: 65px; + top: 0px; width: var(--notebook-sidebar-width); transform: translateX(-100%); transition: transform var(--notebook-popover-transition-properties); + + .NotebookScene & { + // Account for sticky header + top: 4rem; + } } &--showing { diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookSidebar.tsx b/frontend/src/scenes/notebooks/Notebook/NotebookSidebar.tsx index 1dd9459879a42..35950005e532a 100644 --- a/frontend/src/scenes/notebooks/Notebook/NotebookSidebar.tsx +++ b/frontend/src/scenes/notebooks/Notebook/NotebookSidebar.tsx @@ -3,10 +3,11 @@ import { BuiltLogic, useActions, useValues } from 'kea' import clsx from 'clsx' import { notebookLogic } from './notebookLogic' import { notebookNodeLogicType } from '../Nodes/notebookNodeLogicType' +import { LemonButton } from '@posthog/lemon-ui' +import { IconEyeVisible } from 'lib/lemon-ui/icons' export const NotebookSidebar = (): JSX.Element | null => { - const { selectedNodeLogic, isShowingSidebar, isEditable } = useValues(notebookLogic) - const { setIsShowingSidebar } = useActions(notebookLogic) + const { editingNodeLogic, isShowingSidebar, isEditable } = useValues(notebookLogic) if (!isEditable) { return null @@ -18,29 +19,40 @@ export const NotebookSidebar = (): JSX.Element | null => { 'NotebookSidebar--showing': isShowingSidebar, })} > +
- {selectedNodeLogic && isShowingSidebar && ( - setIsShowingSidebar(false)} /> - )} + {editingNodeLogic && isShowingSidebar && }
) } -export const Widgets = ({ - logic, - onClose, -}: { - logic: BuiltLogic - onClose: () => void -}): JSX.Element | null => { +const Widgets = ({ logic }: { logic: BuiltLogic }): JSX.Element | null => { + const { setEditingNodeId } = useActions(notebookLogic) const { widgets, nodeAttributes } = useValues(logic) - const { updateAttributes } = useActions(logic) + const { updateAttributes, selectNode } = useActions(logic) return (
{widgets.map(({ key, label, Component }) => ( - + + } + size="small" + status="primary" + onClick={() => selectNode()} + /> + setEditingNodeId(null)}> + Done + + + } + >
diff --git a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts index 68cb94dbc3265..91b656b354ac1 100644 --- a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts +++ b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts @@ -73,7 +73,7 @@ export const notebookLogic = kea([ clearLocalContent: true, loadNotebook: true, saveNotebook: (notebook: Pick) => ({ notebook }), - setSelectedNodeId: (selectedNodeId: string | null) => ({ selectedNodeId }), + setEditingNodeId: (editingNodeId: string | null) => ({ editingNodeId }), exportJSON: true, showConflictWarning: true, onUpdateEditor: true, @@ -133,10 +133,10 @@ export const notebookLogic = kea([ loadNotebookSuccess: () => false, }, ], - selectedNodeId: [ + editingNodeId: [ null as string | null, { - setSelectedNodeId: (_, { selectedNodeId }) => selectedNodeId, + setEditingNodeId: (_, { editingNodeId }) => editingNodeId, }, ], nodeLogics: [ @@ -170,7 +170,7 @@ export const notebookLogic = kea([ isShowingSidebar: [ false, { - setSelectedNodeId: (showing, { selectedNodeId }) => (selectedNodeId ? showing : false), + setEditingNodeId: (_, { editingNodeId }) => (editingNodeId ? true : false), setIsShowingSidebar: (_, { showing }) => showing, }, ], @@ -319,10 +319,10 @@ export const notebookLogic = kea([ return 'unsaved' }, ], - selectedNodeLogic: [ - (s) => [s.selectedNodeId, s.nodeLogics], - (selectedNodeId, nodeLogics) => - Object.values(nodeLogics).find((nodeLogic) => nodeLogic.props.nodeId === selectedNodeId), + editingNodeLogic: [ + (s) => [s.editingNodeId, s.nodeLogics], + (editingNodeId, nodeLogics) => + Object.values(nodeLogics).find((nodeLogic) => nodeLogic.props.nodeId === editingNodeId), ], findNodeLogic: [ (s) => [s.nodeLogics], @@ -467,7 +467,6 @@ export const notebookLogic = kea([ onEditorSelectionUpdate: () => { if (values.editor) { const node = values.editor.getSelectedNode() - actions.setSelectedNodeId(node?.attrs.nodeId ?? null) if (node?.attrs.nodeId) { actions.scrollToSelection() diff --git a/frontend/src/scenes/notebooks/Notebook/utils.ts b/frontend/src/scenes/notebooks/Notebook/utils.ts index ed78f61d20f89..d0b6683923c0b 100644 --- a/frontend/src/scenes/notebooks/Notebook/utils.ts +++ b/frontend/src/scenes/notebooks/Notebook/utils.ts @@ -47,7 +47,7 @@ export type NotebookNodeViewProps = Omit export type NotebookNodeWidget = { key: string - label: string + label?: string // using 'any' here shouldn't be necessary but, I couldn't figure out how to set a generic on the notebookNodeLogic props Component: ({ attributes, updateAttributes }: NotebookNodeAttributeProperties) => JSX.Element }