Skip to content

Commit

Permalink
changes
Browse files Browse the repository at this point in the history
  • Loading branch information
adamleithp committed Mar 3, 2025
1 parent f870cc5 commit 6f57995
Show file tree
Hide file tree
Showing 9 changed files with 824 additions and 196 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { IconPlusSmall } from '@posthog/icons'
import { LemonBanner, LemonButton } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { Resizer } from 'lib/components/Resizer/Resizer'
import { More } from 'lib/lemon-ui/LemonButton/More'
import { LemonTree } from 'lib/lemon-ui/LemonTree/LemonTree'
import { ContextMenuGroup, ContextMenuItem } from 'lib/ui/ContextMenu/ContextMenu'
import { useRef } from 'react'

import { themeLogic } from '~/layout/navigation-3000/themeLogic'
import { FileSystemEntry } from '~/queries/schema/schema-general'

import { navigation3000Logic } from '../../navigationLogic'
import { KeyboardShortcut } from '../KeyboardShortcut'
import { NavbarBottom } from '../NavbarBottom'
import { projectTreeLogic } from './projectTreeLogic'
import { joinPath, splitPath } from './utils'
Expand All @@ -19,30 +20,36 @@ export function ProjectTree({ contentRef }: { contentRef: React.RefObject<HTMLEl
const { theme } = useValues(themeLogic)
const { isNavShown, mobileLayout } = useValues(navigation3000Logic)
const { toggleNavCollapsed, hideNavOnMobile } = useActions(navigation3000Logic)
const { treeData, loadingPaths, expandedFolders, lastViewedPath, viableItems, helpNoticeVisible } =
useValues(projectTreeLogic)
const {
treeData,
loadingPaths,
expandedFolders,
lastViewedId,
viableItems,
helpNoticeVisible,
dragAndDropEnabled,
pendingActionsCount,
pendingLoaderLoading,
} = useValues(projectTreeLogic)

const {
addFolder,
deleteItem,
moveItem,
toggleFolder,
updateSelectedFolder,
updateLastViewedPath,
toggleFolderOpen,
updateLastViewedId,
updateExpandedFolders,
updateHelpNoticeVisibility,
toggleDragAndDrop,
applyPendingActions,
cancelPendingActions,
} = useActions(projectTreeLogic)
const containerRef = useRef<HTMLDivElement | null>(null)

// Items that should not be draggable or droppable, or have a side action
// TODO: sync with projectTreeLogic
const specialItemsIds: string[] = [
'project',
'project/Explore',
'project/Create new',
'__separator__',
'__apply_pending_actions__',
]
const notDraggableIds: string[] = ['project', 'project/Explore', 'project/Create new', 'project/Unfiled']
const notDroppableIds: string[] = ['project', 'project/Explore', 'project/Create new']

return (
<>
Expand All @@ -52,30 +59,91 @@ export function ProjectTree({ contentRef }: { contentRef: React.RefObject<HTMLEl
// eslint-disable-next-line react/forbid-dom-props
style={theme?.sidebarStyle}
>
<div className="flex gap-1 p-1 items-center justify-between">
<h2 className="text-base font-bold m-0 pl-1">Files</h2>
<div className="flex gap-1 items-center">
{pendingActionsCount > 0 ? (
<span>
{pendingActionsCount} <span>{pendingActionsCount > 1 ? 'changes' : 'change'}</span>
</span>
) : null}
<LemonButton
onClick={() => {
cancelPendingActions()
toggleDragAndDrop(!dragAndDropEnabled)
}}
type="secondary"
size="small"
tooltip={
dragAndDropEnabled
? 'Click to cancel editing and changes'
: 'Click to editinga and drag and drop'
}
>
{dragAndDropEnabled ? `Cancel` : 'Edit'}
</LemonButton>
<LemonButton
size="small"
type="secondary"
disabledReason={pendingActionsCount === 0 ? 'Nothing to save' : undefined}
className={pendingActionsCount === 0 ? 'opacity-30' : ''}
loading={pendingLoaderLoading}
tooltip={pendingActionsCount === 0 ? undefined : 'Save recent actions'}
onClick={
!pendingLoaderLoading
? () => {
applyPendingActions()
toggleDragAndDrop(!dragAndDropEnabled)
}
: undefined
}
>
Save
</LemonButton>
<LemonButton
size="small"
type="secondary"
tooltip="Create new root folder"
onClick={() => {
const folder = prompt('Create a new folder:')
if (folder) {
addFolder(folder)
}
}}
icon={<IconPlusSmall />}
/>
</div>
</div>

<div className="border-b border-primary h-px" />

<LemonTree
contentRef={contentRef}
className="px-0 py-1"
data={treeData}
expandedItemIds={expandedFolders}
isFinishedBuildingTreeData={Object.keys(loadingPaths).length === 0}
defaultSelectedFolderOrNodeId={lastViewedPath || undefined}
defaultSelectedFolderOrNodeId={lastViewedId || undefined}
onNodeClick={(node) => {
if (node?.record?.type === 'project' || node?.record?.type === 'folder') {
updateLastViewedPath(node.record?.path)
if (node?.record?.path) {
updateLastViewedId(node?.id || '')
}
}}
onFolderClick={(folder, isExpanded) => {
if (folder) {
updateSelectedFolder(folder.record?.path || '')
toggleFolder(folder.record?.path || '', isExpanded)
toggleFolderOpen(folder?.id || '', isExpanded)
}
}}
onSetExpandedItemIds={updateExpandedFolders}
enableDragAndDrop={true}
enableDragAndDrop={dragAndDropEnabled}
onDragEnd={(dragEvent) => {
const oldPath = dragEvent.active.id as string
const folder = dragEvent.over?.id

if (oldPath === folder) {
return false
}

if (folder === '') {
const oldSplit = splitPath(oldPath)
const oldFile = oldSplit.pop()
Expand All @@ -100,14 +168,15 @@ export function ProjectTree({ contentRef }: { contentRef: React.RefObject<HTMLEl
return (
item.record?.type !== 'project' &&
item.record?.path &&
!specialItemsIds.includes(item.id || '')
!notDraggableIds.includes(item.id || '') &&
dragAndDropEnabled
)
}}
isItemDroppable={(item) => {
const path = item.record?.path || ''

// disable dropping for special items
if (specialItemsIds.includes(item.id || '')) {
// disable dropping for these IDS
if (notDroppableIds.includes(item.id || '')) {
return false
}

Expand All @@ -121,8 +190,76 @@ export function ProjectTree({ contentRef }: { contentRef: React.RefObject<HTMLEl
}
return false
}}
itemContextMenu={(item) => {
if (notDraggableIds.includes(item.id || '')) {
return undefined
}
return (
<ContextMenuGroup>
{item.record?.type === 'folder' || item.record?.type === 'project' ? (
<ContextMenuItem
onClick={(e) => {
e.stopPropagation()
const folder = prompt(
item.record?.path
? `Create a folder under "${item.record?.path}":`
: 'Create a new folder:',
''
)
if (folder) {
addFolder(
item.record?.path ? `${item.record?.path}/${folder}` : folder
)
}
}}
>
New Folder
</ContextMenuItem>
) : null}
{item.record?.path ? (
<ContextMenuItem
onClick={() => {
const oldPath = item.record?.path
const splits = splitPath(oldPath)
if (splits.length > 0) {
const folder = prompt('New name?', splits[splits.length - 1])
if (folder) {
moveItem(oldPath, joinPath([...splits.slice(0, -1), folder]))
}
}
}}
>
Rename
</ContextMenuItem>
) : null}
{item.record?.path ? (
<ContextMenuItem
onClick={(e) => {
e.stopPropagation()
if (item.record?.path) {
void navigator.clipboard.writeText(item.record?.path)
}
}}
>
Copy Path
</ContextMenuItem>
) : null}
{item.record?.created_at ? (
<ContextMenuItem
onClick={(e) => {
e.stopPropagation()
deleteItem(item.record as unknown as FileSystemEntry)
}}
>
Delete
</ContextMenuItem>
) : null}
{/* Add more menu items as needed */}
</ContextMenuGroup>
)
}}
itemSideAction={(item) => {
if (specialItemsIds.includes(item.id || '')) {
if (notDraggableIds.includes(item.id || '')) {
return undefined
}
return {
Expand All @@ -145,7 +282,7 @@ export function ProjectTree({ contentRef }: { contentRef: React.RefObject<HTMLEl
if (folder) {
addFolder(
item.record?.path
? joinPath([item.record?.path, folder])
? `${item.record?.path}/${folder}`
: folder
)
}
Expand Down Expand Up @@ -229,9 +366,7 @@ export function ProjectTree({ contentRef }: { contentRef: React.RefObject<HTMLEl
All your files are still here, open 'unfiled' to see them, and organize them
the way you'd like.
</li>
<li>
Hold down <KeyboardShortcut command /> to enable drag and drop.
</li>
<li>Right click on tree item for more options.</li>
</ul>
</LemonBanner>
</div>
Expand Down
Loading

0 comments on commit 6f57995

Please sign in to comment.