diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelNotebook.tsx b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelNotebook.tsx index 4e7ad8e0833da..f3a0b11c7bbc0 100644 --- a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelNotebook.tsx +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelNotebook.tsx @@ -1,32 +1,8 @@ -import { useActions, useValues } from 'kea' -import { NotebookPopoverCard } from 'scenes/notebooks/Notebook/NotebookPopover' -import { notebookPopoverLogic } from 'scenes/notebooks/Notebook/notebookPopoverLogic' -import { sidePanelLogic } from '../sidePanelLogic' -import { useEffect } from 'react' +import { NotebookPanel } from 'scenes/notebooks/NotebookPanel/NotebookPanel' export const SidePanelNotebook = (): JSX.Element => { - const { closeSidePanel } = useActions(sidePanelLogic) - const { selectedTab } = useValues(sidePanelLogic) + // const { closeSidePanel } = useActions(sidePanelLogic) + // const { selectedTab } = useValues(sidePanelLogic) - const { visibility } = useValues(notebookPopoverLogic) - const { setVisibility } = useActions(notebookPopoverLogic) - - // useEffect(() => { - // // When something sets the popover to hidden - close the side panel - // return () => { - // if (selectedTab === 'notebook') { - // closeSidePanel() - // } - // } - // }, [visibility === 'hidden']) - - useEffect(() => { - setVisibility('visible') - // On unmount - hide the popover - return () => { - setVisibility('hidden') - } - }, []) - - return + return } diff --git a/frontend/src/layout/navigation/SideBar/SideBar.tsx b/frontend/src/layout/navigation/SideBar/SideBar.tsx index e7cd5d9c3846d..4c7be14ee0798 100644 --- a/frontend/src/layout/navigation/SideBar/SideBar.tsx +++ b/frontend/src/layout/navigation/SideBar/SideBar.tsx @@ -50,7 +50,7 @@ import { Tooltip } from 'lib/lemon-ui/Tooltip' import { Spinner } from 'lib/lemon-ui/Spinner/Spinner' import { DebugNotice } from 'lib/components/DebugNotice' import ActivationSidebar from 'lib/components/ActivationSidebar/ActivationSidebar' -import { NotebookPopover } from 'scenes/notebooks/Notebook/NotebookPopover' +import { NotebookPopover } from 'scenes/notebooks/NotebookPanel/NotebookPopover' import { FlaggedFeature } from 'lib/components/FlaggedFeature' import { IconNotebook } from 'scenes/notebooks/IconNotebook' diff --git a/frontend/src/layout/navigation/TopBar/NotebookButton.tsx b/frontend/src/layout/navigation/TopBar/NotebookButton.tsx index 278644197c756..a2125be240ec8 100644 --- a/frontend/src/layout/navigation/TopBar/NotebookButton.tsx +++ b/frontend/src/layout/navigation/TopBar/NotebookButton.tsx @@ -1,5 +1,5 @@ import { useActions, useValues } from 'kea' -import { notebookPopoverLogic } from 'scenes/notebooks/Notebook/notebookPopoverLogic' +import { notebookPopoverLogic } from 'scenes/notebooks/NotebookPanel/notebookPopoverLogic' import { LemonButton, LemonButtonWithSideActionProps } from '@posthog/lemon-ui' import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' import { IconNotebook } from 'scenes/notebooks/IconNotebook' diff --git a/frontend/src/models/notebooksModel.ts b/frontend/src/models/notebooksModel.ts index 249b6055b6847..7f86b042dbee2 100644 --- a/frontend/src/models/notebooksModel.ts +++ b/frontend/src/models/notebooksModel.ts @@ -8,7 +8,7 @@ import posthog from 'posthog-js' import { LOCAL_NOTEBOOK_TEMPLATES } from 'scenes/notebooks/NotebookTemplates/notebookTemplates' import { deleteWithUndo } from 'lib/utils' import { teamLogic } from 'scenes/teamLogic' -import { notebookPopoverLogic } from 'scenes/notebooks/Notebook/notebookPopoverLogic' +import { notebookPopoverLogic } from 'scenes/notebooks/NotebookPanel/notebookPopoverLogic' import { defaultNotebookContent, EditorFocusPosition, JSONContent } from 'scenes/notebooks/Notebook/utils' import type { notebooksModelType } from './notebooksModelType' diff --git a/frontend/src/scenes/notebooks/AddToNotebook/DraggableToNotebook.tsx b/frontend/src/scenes/notebooks/AddToNotebook/DraggableToNotebook.tsx index b4b6f619320df..c2e43371f8bde 100644 --- a/frontend/src/scenes/notebooks/AddToNotebook/DraggableToNotebook.tsx +++ b/frontend/src/scenes/notebooks/AddToNotebook/DraggableToNotebook.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { NotebookNodeType } from '~/types' import './DraggableToNotebook.scss' import { useActions, useValues } from 'kea' -import { notebookPopoverLogic } from '../Notebook/notebookPopoverLogic' +import { notebookPopoverLogic } from '../NotebookPanel/notebookPopoverLogic' import clsx from 'clsx' import { FlaggedFeature } from 'lib/components/FlaggedFeature' import { FEATURE_FLAGS } from 'lib/constants' diff --git a/frontend/src/scenes/notebooks/NotebookPanel/NotebookPanel.scss b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPanel.scss new file mode 100644 index 0000000000000..826edb17825e1 --- /dev/null +++ b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPanel.scss @@ -0,0 +1,67 @@ +@import '../../../styles/mixins'; + +.NotebookPanel { + flex: 1; + display: flex; + flex-direction: column; + border-radius: var(--radius); + background-color: var(--bg-3000); + border: 1px solid var(--border-3000); + box-shadow: 0px 16px 16px rgba(0, 0, 0, 0); + transition: box-shadow 150ms linear; + overflow: hidden; +} + +.NotebookPanelDropzone { + box-shadow: 0px 16px 16px rgba(0, 0, 0, 0.15); + border: 2px dashed var(--border-3000); + border-radius: var(--radius); + + transition: all 150ms; + height: 4rem; + margin-bottom: 1rem; + backdrop-filter: blur(5px); + display: flex; + + .NotebookPanelDropzone__message { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-weight: 700; + font-size: 1rem; + color: var(--muted-alt); + text-align: center; + pointer-events: none; + background-color: var(--bg-light); + padding: 1rem; + opacity: 0.75; + transition: all 150ms; + } + + .NotebookPanelDropzone__dropped { + overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; + } + + &--active { + border-color: var(--primary); + height: 8rem; + + .NotebookPanelDropzone__message { + opacity: 1; + } + } + + &--dropped { + padding: 1rem; + border-color: var(--primary); + background-color: var(--bg-light); + height: 100%; + justify-content: flex-start; + align-items: initial; + } +} diff --git a/frontend/src/scenes/notebooks/NotebookPanel/NotebookPanel.tsx b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPanel.tsx new file mode 100644 index 0000000000000..7337ad67943f9 --- /dev/null +++ b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPanel.tsx @@ -0,0 +1,83 @@ +import { useActions, useValues } from 'kea' +import './NotebookPanel.scss' +import { Notebook } from '../Notebook/Notebook' +import { LemonButton } from '@posthog/lemon-ui' +import { IconFullScreen, IconShare } from 'lib/lemon-ui/icons' +import { useMemo } from 'react' +import { NotebookListMini } from '../Notebook/NotebookListMini' +import { notebooksModel } from '~/models/notebooksModel' +import { NotebookExpandButton, NotebookSyncInfo } from '../Notebook/NotebookMeta' +import { notebookLogic } from '../Notebook/notebookLogic' +import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver' +import { openNotebookShareDialog } from '../Notebook/NotebookShare' +import { notebookPanelLogic } from './notebookPanelLogic' + +export function NotebookPanel(): JSX.Element | null { + const { fullScreen, selectedNotebook, initialAutofocus, droppedResource, dropProperties } = + useValues(notebookPanelLogic) + const { setFullScreen, selectNotebook } = useActions(notebookPanelLogic) + const { createNotebook } = useActions(notebooksModel) + const { notebook } = useValues(notebookLogic({ shortId: selectedNotebook })) + // const { activeScene } = useValues(sceneLogic) + + // const showEditor = activeScene === Scene.Notebook ? visibility !== 'hidden' : shownAtLeastOnce + const editable = !notebook?.is_template + + const { ref, size } = useResizeBreakpoints({ + 0: 'small', + 832: 'medium', + }) + + const contentWidthHasEffect = useMemo(() => fullScreen && size === 'medium', [fullScreen, size]) + + if (droppedResource) { + return null + } + + return ( +
+
+ + selectNotebook(notebook.short_id)} + onNewNotebook={() => createNotebook()} + /> + + + {selectedNotebook && } + + openNotebookShareDialog({ shortId: selectedNotebook })} + status="primary-alt" + icon={} + tooltip="Share notebook" + tooltipPlacement="left" + /> + + {contentWidthHasEffect && } + + setFullScreen(!fullScreen)} + status="primary-alt" + active={fullScreen} + icon={} + tooltip="Toggle full screen" + tooltipPlacement="left" + /> + +
+ +
+ +
+
+ ) +} diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookPopover.scss b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPopover.scss similarity index 100% rename from frontend/src/scenes/notebooks/Notebook/NotebookPopover.scss rename to frontend/src/scenes/notebooks/NotebookPanel/NotebookPopover.scss diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookPopover.tsx b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPopover.tsx similarity index 93% rename from frontend/src/scenes/notebooks/Notebook/NotebookPopover.tsx rename to frontend/src/scenes/notebooks/NotebookPanel/NotebookPopover.tsx index f820f876cce36..5cc828d4e7535 100644 --- a/frontend/src/scenes/notebooks/Notebook/NotebookPopover.tsx +++ b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPopover.tsx @@ -1,20 +1,20 @@ import { useActions, useValues } from 'kea' import clsx from 'clsx' import './NotebookPopover.scss' -import { Notebook } from './Notebook' -import { notebookPopoverLogic } from 'scenes/notebooks/Notebook/notebookPopoverLogic' +import { Notebook } from '../Notebook/Notebook' +import { notebookPopoverLogic } from 'scenes/notebooks/NotebookPanel/notebookPopoverLogic' import { LemonButton } from '@posthog/lemon-ui' import { IconFullScreen, IconChevronRight, IconOpenInNew, IconShare } from 'lib/lemon-ui/icons' import { useEffect, useMemo, useRef } from 'react' import { useKeyboardHotkeys } from 'lib/hooks/useKeyboardHotkeys' -import { NotebookListMini } from './NotebookListMini' +import { NotebookListMini } from '../Notebook/NotebookListMini' import { notebooksModel } from '~/models/notebooksModel' -import { NotebookExpandButton, NotebookSyncInfo } from './NotebookMeta' -import { notebookLogic } from './notebookLogic' +import { NotebookExpandButton, NotebookSyncInfo } from '../Notebook/NotebookMeta' +import { notebookLogic } from '../Notebook/notebookLogic' import { urls } from 'scenes/urls' import { NotebookPopoverDropzone } from './NotebookPopoverDropzone' import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver' -import { openNotebookShareDialog } from './NotebookShare' +import { openNotebookShareDialog } from '../Notebook/NotebookShare' import { sceneLogic } from 'scenes/sceneLogic' import { Scene } from 'scenes/sceneTypes' diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookPopoverDropzone.tsx b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPopoverDropzone.tsx similarity index 97% rename from frontend/src/scenes/notebooks/Notebook/NotebookPopoverDropzone.tsx rename to frontend/src/scenes/notebooks/NotebookPanel/NotebookPopoverDropzone.tsx index 12adec1008664..e89f4f7d421c7 100644 --- a/frontend/src/scenes/notebooks/Notebook/NotebookPopoverDropzone.tsx +++ b/frontend/src/scenes/notebooks/NotebookPanel/NotebookPopoverDropzone.tsx @@ -4,7 +4,7 @@ import { notebookPopoverLogic } from './notebookPopoverLogic' import { useActions, useValues } from 'kea' import { NotebookNodeType } from '~/types' import { NotebookSelectList } from '../NotebookSelectButton/NotebookSelectButton' -import { notebookLogicType } from './notebookLogicType' +import { notebookLogicType } from '../Notebook/notebookLogicType' import { LemonButton } from '@posthog/lemon-ui' export function NotebookPopoverDropzone(): JSX.Element | null { diff --git a/frontend/src/scenes/notebooks/NotebookPanel/notebookPanelLogic.ts b/frontend/src/scenes/notebooks/NotebookPanel/notebookPanelLogic.ts new file mode 100644 index 0000000000000..3045127b1d88b --- /dev/null +++ b/frontend/src/scenes/notebooks/NotebookPanel/notebookPanelLogic.ts @@ -0,0 +1,139 @@ +import { actions, kea, reducers, path, listeners, selectors, connect } from 'kea' + +import { HTMLProps, RefObject } from 'react' +import { EditorFocusPosition } from '../Notebook/utils' + +import type { notebookPanelLogicType } from './notebookPanelLogicType' +import { NotebookNodeResource } from '~/types' +import { SidePanelTab, sidePanelLogic } from '~/layout/navigation-3000/sidepanel/sidePanelLogic' + +export const MIN_NOTEBOOK_SIDEBAR_WIDTH = 600 + +export const notebookPanelLogic = kea([ + path(['scenes', 'notebooks', 'Notebook', 'notebookPanelLogic']), + connect({ + values: [sidePanelLogic, ['sidePanelOpen']], + actions: [sidePanelLogic, ['openSidePanel']], + }), + actions({ + setFullScreen: (full: boolean) => ({ full }), + selectNotebook: (id: string, autofocus: EditorFocusPosition | undefined = undefined) => ({ id, autofocus }), + startDropMode: true, + endDropMode: true, + setDropDistance: (distance: number) => ({ distance }), + setDroppedResource: (resource: NotebookNodeResource | string | null) => ({ resource }), + }), + + reducers(() => ({ + selectedNotebook: [ + 'scratchpad', + { persist: true }, + { + selectNotebook: (_, { id }) => id, + }, + ], + fullScreen: [ + false, + { + setFullScreen: (_, { full }) => full, + setVisibility: (state, { visibility }) => (visibility === 'hidden' ? false : state), + }, + ], + initialAutofocus: [ + 'start' as EditorFocusPosition, + { + selectNotebook: (_, { autofocus }) => autofocus ?? 'start', + }, + ], + elementRef: [ + null as RefObject | null, + { + setElementRef: (_, { element }) => element, + }, + ], + shownAtLeastOnce: [ + false, + { + setVisibility: (state, { visibility }) => visibility !== 'hidden' || state, + }, + ], + dropMode: [ + false, + { + startDropMode: () => true, + endDropMode: () => false, + }, + ], + dropDistance: [ + 0, + { + startDropMode: () => -1, + endDropMode: () => -1, + setDropDistance: (_, { distance }) => distance, + }, + ], + droppedResource: [ + null as NotebookNodeResource | string | null, + { + setVisibility: (state, { visibility }) => (visibility === 'hidden' ? null : state), + setDroppedResource: (_, { resource }) => resource, + }, + ], + })), + + selectors(({ cache, actions }) => ({ + dropProperties: [ + (s) => [s.dropMode, s.dropDistance, s.sidePanelOpen], + ( + dropMode, + dropDistance, + sidePanelOpen + ): Pick, 'onDragEnter' | 'onDragLeave' | 'style'> => { + return dropMode + ? { + onDragEnter: () => { + cache.dragEntercount = (cache.dragEntercount || 0) + 1 + if (cache.dragEntercount === 1) { + actions.openSidePanel(SidePanelTab.Notebooks) + } + }, + + onDragLeave: () => { + cache.dragEntercount = (cache.dragEntercount || 0) - 1 + + if (cache.dragEntercount <= 0) { + cache.dragEntercount = 0 + actions.openSidePanel(SidePanelTab.Notebooks) + } + }, + } + : {} + }, + ], + })), + + listeners(({ cache, actions }) => ({ + startDropMode: () => { + cache.dragEntercount = 0 + cache.dragStart = null + actions.openSidePanel(SidePanelTab.Notebooks) + + cache.dragListener = (event: MouseEvent) => { + if (!cache.dragStart) { + cache.dragStart = event.pageX + } + + // The drop distance is the percentage between where the drag started and where it now is + const dropDistance = (event.pageX - cache.dragStart) / window.innerWidth + actions.setDropDistance(dropDistance) + } + window.addEventListener('drag', cache.dragListener) + }, + endDropMode: () => { + // if (values.visibility === 'peek') { + // actions.setVisibility('hidden') + // } + window.removeEventListener('drag', cache.dragListener) + }, + })), +]) diff --git a/frontend/src/scenes/notebooks/Notebook/notebookPopoverLogic.ts b/frontend/src/scenes/notebooks/NotebookPanel/notebookPopoverLogic.ts similarity index 99% rename from frontend/src/scenes/notebooks/Notebook/notebookPopoverLogic.ts rename to frontend/src/scenes/notebooks/NotebookPanel/notebookPopoverLogic.ts index 1a0f4865b5092..916697879891f 100644 --- a/frontend/src/scenes/notebooks/Notebook/notebookPopoverLogic.ts +++ b/frontend/src/scenes/notebooks/NotebookPanel/notebookPopoverLogic.ts @@ -4,7 +4,7 @@ import { urlToAction } from 'kea-router' import { HTMLProps, RefObject } from 'react' import posthog from 'posthog-js' import { subscriptions } from 'kea-subscriptions' -import { EditorFocusPosition } from './utils' +import { EditorFocusPosition } from '../Notebook/utils' import type { notebookPopoverLogicType } from './notebookPopoverLogicType' import { NotebookNodeResource, NotebookPopoverVisibility } from '~/types' diff --git a/frontend/src/scenes/notebooks/NotebookScene.tsx b/frontend/src/scenes/notebooks/NotebookScene.tsx index 805a1186f9039..49d088f4375ac 100644 --- a/frontend/src/scenes/notebooks/NotebookScene.tsx +++ b/frontend/src/scenes/notebooks/NotebookScene.tsx @@ -5,7 +5,7 @@ import { Notebook } from './Notebook/Notebook' import { NotFound } from 'lib/components/NotFound' import { NotebookSceneLogicProps, notebookSceneLogic } from './notebookSceneLogic' import { LemonButton, LemonTag } from '@posthog/lemon-ui' -import { notebookPopoverLogic } from './Notebook/notebookPopoverLogic' +import { notebookPopoverLogic } from './NotebookPanel/notebookPopoverLogic' import { NotebookExpandButton, NotebookSyncInfo } from './Notebook/NotebookMeta' import { UserActivityIndicator } from 'lib/components/UserActivityIndicator/UserActivityIndicator' import { diff --git a/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx b/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx index 6f57e08427f01..fd94c94bd1535 100644 --- a/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx +++ b/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx @@ -10,7 +10,7 @@ import { useEffect } from 'react' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { LemonMenu } from 'lib/lemon-ui/LemonMenu' import { IconDelete, IconEllipsis } from 'lib/lemon-ui/icons' -import { notebookPopoverLogic } from '../Notebook/notebookPopoverLogic' +import { notebookPopoverLogic } from '../NotebookPanel/notebookPopoverLogic' import { membersLogic } from 'scenes/organization/Settings/membersLogic' import { ContainsTypeFilters } from 'scenes/notebooks/NotebooksTable/ContainsTypeFilter' import { DEFAULT_FILTERS, notebooksTableLogic } from 'scenes/notebooks/NotebooksTable/notebooksTableLogic'