Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ export function useResourceTreeSections({
}

interface ResourceMenuSectionsProps {
flat?: boolean
/** Foldered families, from {@link useResourceTreeSections}. */
sections: ResourceTreeSection[]
/** Every available family. Foldered ones are taken from `sections` instead. */
Expand All @@ -226,7 +225,6 @@ interface ResourceMenuSectionsProps {
* canonical order.
*/
export function ResourceMenuSections({
flat = false,
sections,
groups,
onSelect,
Expand All @@ -235,7 +233,7 @@ export function ResourceMenuSections({
const sectionByType = new Map(sections.map((section) => [section.type, section]))
const entries = groups
.filter(({ type, items }) =>
!flat && FOLDERED_RESOURCE_TYPES.has(type) ? sectionByType.has(type) : items.length > 0
FOLDERED_RESOURCE_TYPES.has(type) ? sectionByType.has(type) : items.length > 0
)
.sort(byResourceMenuOrder)

Expand Down Expand Up @@ -398,6 +396,8 @@ interface WorkspaceResourceMenuContentProps {
enabled: boolean
excludeTypes?: readonly MothershipResourceType[]
searchable?: boolean
/** Offers every folder as an attachable entry, as chat does. */
selectFolders?: boolean
Comment thread
waleedlatif1 marked this conversation as resolved.
onSelect: (resource: MothershipResource) => void
}

Expand All @@ -406,20 +406,27 @@ function WorkspaceResourceMenuContent({
enabled,
excludeTypes,
searchable = true,
selectFolders,
onSelect,
}: WorkspaceResourceMenuContentProps) {
const { groups, structureFolders, isHydrating } = useAvailableResources(workspaceId, {
enabled,
excludeTypes,
})
const sections = useResourceTreeSections({ groups, structureFolders })
const sections = useResourceTreeSections({ groups, structureFolders, selectFolders })
const select = (resource: MothershipResource) =>
onSelect(
resource.type === 'browser' || resource.type === 'terminal'
? resource
: { ...resource, workspaceId }
)
const menu = <ResourceMenuSections sections={sections} groups={groups} onSelect={select} />
/** Lists fill in as they load, so a trailing row keeps a loading workspace from reading as empty. */
const menu = (
<>
<ResourceMenuSections sections={sections} groups={groups} onSelect={select} />
{isHydrating && <DropdownMenuItem disabled>Loading resources…</DropdownMenuItem>}
</>
)
return searchable ? (
<ResourceMenuSearch groups={groups} isHydrating={isHydrating} onSelect={select}>
{menu}
Expand All @@ -429,15 +436,24 @@ function WorkspaceResourceMenuContent({
)
}

function WorkspaceResourceSubmenu({
workspace,
excludeTypes,
onSelect,
}: {
interface WorkspaceResourceSubmenuProps {
workspace: { id: string; name: string }
/** Must be referentially stable (a module constant) — it keys the group memo. */
excludeTypes?: readonly MothershipResourceType[]
selectFolders?: boolean
onSelect: (resource: MothershipResource) => void
}) {
}

/**
* One workspace of an organization-wide picker: its own foldered resource menu,
* fetched when the submenu first opens. Selections carry the workspace as owner.
*/
export function WorkspaceResourceSubmenu({
workspace,
excludeTypes,
selectFolders,
onSelect,
}: WorkspaceResourceSubmenuProps) {
const [open, setOpen] = useState(false)
return (
<DropdownMenuSub open={open} onOpenChange={setOpen}>
Expand All @@ -450,6 +466,7 @@ function WorkspaceResourceSubmenu({
enabled={open}
excludeTypes={excludeTypes}
searchable={false}
selectFolders={selectFolders}
onSelect={onSelect}
/>
</DropdownMenuSubContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
ResourceFolderTreeItems,
ResourceMenuSections,
useResourceTreeSections,
WorkspaceResourceSubmenu,
} from './add-resource-dropdown'
export { useAvailableResources } from './available-resources'
export { resourceFromItem } from './resource-from-item'
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
resourceFromItem,
useAvailableResources,
useResourceTreeSections,
WorkspaceResourceSubmenu,
} from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown'
import type { AvailableResources } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/available-resources'
import {
Expand Down Expand Up @@ -62,6 +63,22 @@ const MENTION_MAX_HEIGHT_CLASS = 'max-h-[min(280px,var(--radix-popper-available-
* (`ADD_RESOURCE_EXCLUDED_TYPES` in `resource-tabs`).
*/
const MENTION_ONLY_RESOURCE_TYPES = new Set<MothershipResourceType>(['integration'])

/**
* Families an organization chat's workspace submenus leave out: the mention-only
* ones, plus Browser and Terminal, which belong to this desktop rather than to a
* workspace and so sit once after the workspaces.
*/
const WORKSPACE_SUBMENU_EXCLUDED_TYPES: readonly MothershipResourceType[] = [
...MENTION_ONLY_RESOURCE_TYPES,
'browser',
'terminal',
]

function isNativeResourceGroup({ type }: { type: MothershipResourceType }): boolean {
return type === 'browser' || type === 'terminal'
}

const EMPTY_BROWSER_TABS = [] as const
const EMPTY_TERMINAL_TABS = [] as const

Expand Down Expand Up @@ -138,12 +155,9 @@ export const PlusMenuDropdown = React.memo(
: workspaceInventory
const { structureFolders } = combined
const availableResources = organizationId
? [
...combined.groups,
...workspaceInventory.groups.filter(
(group) => group.type === 'browser' || group.type === 'terminal'
),
].sort(byResourceMenuOrder)
? [...combined.groups, ...workspaceInventory.groups.filter(isNativeResourceGroup)].sort(
byResourceMenuOrder
)
: combined.groups
const isHydrating = combined.isHydrating || Boolean(organizationId && workspacesPending)

Expand Down Expand Up @@ -183,9 +197,14 @@ export const PlusMenuDropdown = React.memo(
terminalTabs,
])

/**
* Built from this workspace's own inventory, which has no foldered families in an
* organization chat: there each workspace submenu builds its own sections, because
* ids are only unique within a workspace.
*/
const treeSections = useResourceTreeSections({
groups: availableResources,
structureFolders,
groups: workspaceInventory.groups,
structureFolders: workspaceInventory.structureFolders,
selectFolders: true,
})

Expand Down Expand Up @@ -386,10 +405,21 @@ export const PlusMenuDropdown = React.memo(
{/* Always-mounted; swapping this subtree with filtered results makes Radix's
menu FocusScope steal focus from the search input back to the content root. */}
<div hidden={filteredItems !== null}>
{organizationId &&
workspaces.map((workspace) => (
<WorkspaceResourceSubmenu
key={workspace.id}
workspace={workspace}
excludeTypes={WORKSPACE_SUBMENU_EXCLUDED_TYPES}
selectFolders
onSelect={handleSelect}
/>
))}
<ResourceMenuSections
flat={Boolean(organizationId)}
sections={organizationId ? [] : treeSections}
groups={visibleResources}
sections={treeSections}
groups={
organizationId ? visibleResources.filter(isNativeResourceGroup) : visibleResources
}
onSelect={handleSelect}
subContentClassName='max-w-[min(300px,calc(100vw-32px))]'
/>
Expand Down
Loading