From 9033d0822f15a6b6bcf8f76bb99b8fc37ba24c58 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 22 Nov 2023 12:30:20 +0100 Subject: [PATCH] feat: Better floating of editing widget (#18780) --- .../scenes/notebooks/Nodes/NodeWrapper.scss | 2 +- .../scenes/notebooks/Nodes/NodeWrapper.tsx | 15 ++++-- .../notebooks/Nodes/notebookNodeLogic.ts | 12 ++++- .../scenes/notebooks/Notebook/Notebook.scss | 39 ++++++++------ .../notebooks/Notebook/NotebookColumnLeft.tsx | 52 ++++++++++++++++--- package.json | 2 +- pnpm-lock.yaml | 8 +-- 7 files changed, 97 insertions(+), 33 deletions(-) diff --git a/frontend/src/scenes/notebooks/Nodes/NodeWrapper.scss b/frontend/src/scenes/notebooks/Nodes/NodeWrapper.scss index 179f475205d..1d4fa2d2021 100644 --- a/frontend/src/scenes/notebooks/Nodes/NodeWrapper.scss +++ b/frontend/src/scenes/notebooks/Nodes/NodeWrapper.scss @@ -56,7 +56,7 @@ } &--selected { - --border-color: var(--primary-3000); + --border-color: var(--border-bold); } &--auto-hide-metadata { diff --git a/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx b/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx index cba90e52067..d2c96c06ee0 100644 --- a/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx @@ -75,7 +75,17 @@ function NodeWrapper(props: NodeWrapperP // nodeId can start null, but should then immediately be generated const nodeLogic = useMountedLogic(notebookNodeLogic(logicProps)) const { resizeable, expanded, actions, nodeId } = useValues(nodeLogic) - const { setExpanded, deleteNode, toggleEditing, insertOrSelectNextLine } = useActions(nodeLogic) + const { setRef, setExpanded, deleteNode, toggleEditing, insertOrSelectNextLine } = useActions(nodeLogic) + + const { ref: inViewRef, inView } = useInView({ triggerOnce: true }) + + const setRefs = useCallback( + (node) => { + setRef(node) + inViewRef(node) + }, + [inViewRef] + ) useEffect(() => { // TRICKY: child nodes mount the parent logic so we need to control the mounting / unmounting directly in this component @@ -92,7 +102,6 @@ function NodeWrapper(props: NodeWrapperP mountedNotebookLogic, }) - const [ref, inView] = useInView({ triggerOnce: true }) const contentRef = useRef(null) // If resizeable is true then the node attr "height" is required @@ -136,7 +145,7 @@ function NodeWrapper(props: NodeWrapperP
([ initializeNode: true, setMessageListeners: (listeners: NotebookNodeMessagesListeners) => ({ listeners }), setTitlePlaceholder: (titlePlaceholder: string) => ({ titlePlaceholder }), + setRef: (ref: HTMLElement | null) => ({ ref }), }), connect((props: NotebookNodeLogicProps) => ({ @@ -71,6 +72,13 @@ export const notebookNodeLogic = kea([ })), reducers(({ props }) => ({ + ref: [ + null as HTMLElement | null, + { + setRef: (_, { ref }) => ref, + unregisterNodeLogic: () => null, + }, + ], expanded: [ props.startExpanded ?? true, { @@ -246,7 +254,9 @@ export const notebookNodeLogic = kea([ props.updateAttributes(attributes) }, toggleEditing: ({ visible }) => { - const shouldShowThis = typeof visible === 'boolean' ? visible : !values.notebookLogic.values.editingNodeId + const shouldShowThis = + typeof visible === 'boolean' ? visible : values.notebookLogic.values.editingNodeId !== values.nodeId + props.notebookLogic.actions.setEditingNodeId(shouldShowThis ? values.nodeId : null) }, initializeNode: () => { diff --git a/frontend/src/scenes/notebooks/Notebook/Notebook.scss b/frontend/src/scenes/notebooks/Notebook/Notebook.scss index ed584c90842..9b0139e499d 100644 --- a/frontend/src/scenes/notebooks/Notebook/Notebook.scss +++ b/frontend/src/scenes/notebooks/Notebook/Notebook.scss @@ -147,14 +147,6 @@ } } - &--editable { - .NotebookEditor .ProseMirror { - // Add some padding to help clicking below the last element - padding-bottom: 10rem; - flex: 1; - } - } - .NotebookColumn { position: relative; width: 0; @@ -191,6 +183,11 @@ .NotebookColumn__content { width: var(--notebook-column-left-width); transform: translateX(-100%); + + > .LemonWidget .LemonWidget__content { + max-height: var(--notebook-sidebar-height); + overflow: auto; + } } } @@ -218,12 +215,27 @@ } } + &--editable { + .NotebookEditor .ProseMirror { + // Add some padding to help clicking below the last element + padding-bottom: 10rem; + flex: 1; + } + + .NotebookColumn--left.NotebookColumn--showing { + & + .NotebookEditor { + .ProseMirror { + // Add a lot of padding to allow the entire column to always be on screen + padding-bottom: 100vh; + } + } + } + } + .NotebookHistory { flex: 1; display: flex; flex-direction: column; - height: var(--notebook-sidebar-height); - overflow: hidden; } .NotebookInlineMenu { @@ -236,13 +248,6 @@ } } - .NotebookColumnLeft__widget { - > .LemonWidget__content { - max-height: calc(100vh - 220px); - overflow: auto; - } - } - .LemonTable__content > table > thead { position: sticky; top: 0; diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookColumnLeft.tsx b/frontend/src/scenes/notebooks/Notebook/NotebookColumnLeft.tsx index e952c0cb8fd..814ca011af9 100644 --- a/frontend/src/scenes/notebooks/Notebook/NotebookColumnLeft.tsx +++ b/frontend/src/scenes/notebooks/Notebook/NotebookColumnLeft.tsx @@ -4,8 +4,8 @@ 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' import { NotebookHistory } from './NotebookHistory' +import { useEffect, useRef, useState } from 'react' export const NotebookColumnLeft = (): JSX.Element | null => { const { editingNodeLogic, isShowingLeftColumn, showHistory } = useValues(notebookLogic) @@ -16,7 +16,7 @@ export const NotebookColumnLeft = (): JSX.Element | null => { 'NotebookColumn--showing': isShowingLeftColumn, })} > -
+ {editingNodeLogic ? : null}
{isShowingLeftColumn ? ( editingNodeLogic ? ( @@ -30,6 +30,41 @@ export const NotebookColumnLeft = (): JSX.Element | null => { ) } +export const NotebookNodeSettingsOffset = ({ logic }: { logic: BuiltLogic }): JSX.Element => { + const { ref } = useValues(logic) + const offsetRef = useRef(null) + const [height, setHeight] = useState(0) + + useEffect(() => { + // Interval to check the relative positions of the node and the offset div + // updating the height so that it always is inline + const updateHeight = (): void => { + if (ref && offsetRef.current) { + const newHeight = ref.getBoundingClientRect().top - offsetRef.current.getBoundingClientRect().top + + if (height !== newHeight) { + setHeight(newHeight) + } + } + } + + const interval = setInterval(updateHeight, 100) + updateHeight() + + return () => clearInterval(interval) + }, [ref, offsetRef.current, height]) + + return ( +
+ ) +} + export const NotebookNodeSettingsWidget = ({ logic }: { logic: BuiltLogic }): JSX.Element => { const { setEditingNodeId } = useActions(notebookLogic) const { Settings, nodeAttributes, title } = useValues(logic) @@ -41,16 +76,21 @@ export const NotebookNodeSettingsWidget = ({ logic }: { logic: BuiltLogic - } size="small" status="primary" onClick={() => selectNode()} /> setEditingNodeId(null)}> Done } > - {Settings ? ( - - ) : null} +
selectNode()}> + {Settings ? ( + + ) : null} +
) } diff --git a/package.json b/package.json index e510b90210c..37d635ba287 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "react-dom": "^18.2.0", "react-draggable": "^4.2.0", "react-grid-layout": "^1.3.0", - "react-intersection-observer": "^9.4.3", + "react-intersection-observer": "^9.5.3", "react-markdown": "^5.0.3", "react-modal": "^3.15.1", "react-resizable": "^3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb52460b720..e5066bec55d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -261,8 +261,8 @@ dependencies: specifier: ^1.3.0 version: 1.3.4(react-dom@18.2.0)(react@18.2.0) react-intersection-observer: - specifier: ^9.4.3 - version: 9.4.3(react@18.2.0) + specifier: ^9.5.3 + version: 9.5.3(react@18.2.0) react-markdown: specifier: ^5.0.3 version: 5.0.3(@types/react@17.0.52)(react@18.2.0) @@ -16838,8 +16838,8 @@ packages: react: 18.2.0 dev: true - /react-intersection-observer@9.4.3(react@18.2.0): - resolution: {integrity: sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==} + /react-intersection-observer@9.5.3(react@18.2.0): + resolution: {integrity: sha512-NJzagSdUPS5rPhaLsHXYeJbsvdpbJwL6yCHtMk91hc0ufQ2BnXis+0QQ9NBh6n9n+Q3OyjR6OQLShYbaNBkThQ==} peerDependencies: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 dependencies: