Skip to content

Commit 434db43

Browse files
committed
improvement(chat): nest folders inside their resources in the org resource menu
1 parent a7ea8fd commit 434db43

3 files changed

Lines changed: 63 additions & 21 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/add-resource-dropdown.tsx‎

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ export function useResourceTreeSections({
204204
}
205205

206206
interface ResourceMenuSectionsProps {
207-
flat?: boolean
208207
/** Foldered families, from {@link useResourceTreeSections}. */
209208
sections: ResourceTreeSection[]
210209
/** Every available family. Foldered ones are taken from `sections` instead. */
@@ -226,7 +225,6 @@ interface ResourceMenuSectionsProps {
226225
* canonical order.
227226
*/
228227
export function ResourceMenuSections({
229-
flat = false,
230228
sections,
231229
groups,
232230
onSelect,
@@ -235,7 +233,7 @@ export function ResourceMenuSections({
235233
const sectionByType = new Map(sections.map((section) => [section.type, section]))
236234
const entries = groups
237235
.filter(({ type, items }) =>
238-
!flat && FOLDERED_RESOURCE_TYPES.has(type) ? sectionByType.has(type) : items.length > 0
236+
FOLDERED_RESOURCE_TYPES.has(type) ? sectionByType.has(type) : items.length > 0
239237
)
240238
.sort(byResourceMenuOrder)
241239

@@ -398,6 +396,8 @@ interface WorkspaceResourceMenuContentProps {
398396
enabled: boolean
399397
excludeTypes?: readonly MothershipResourceType[]
400398
searchable?: boolean
399+
/** Offers every folder as an attachable entry, as chat does. */
400+
selectFolders?: boolean
401401
onSelect: (resource: MothershipResource) => void
402402
}
403403

@@ -406,13 +406,14 @@ function WorkspaceResourceMenuContent({
406406
enabled,
407407
excludeTypes,
408408
searchable = true,
409+
selectFolders,
409410
onSelect,
410411
}: WorkspaceResourceMenuContentProps) {
411412
const { groups, structureFolders, isHydrating } = useAvailableResources(workspaceId, {
412413
enabled,
413414
excludeTypes,
414415
})
415-
const sections = useResourceTreeSections({ groups, structureFolders })
416+
const sections = useResourceTreeSections({ groups, structureFolders, selectFolders })
416417
const select = (resource: MothershipResource) =>
417418
onSelect(
418419
resource.type === 'browser' || resource.type === 'terminal'
@@ -429,15 +430,24 @@ function WorkspaceResourceMenuContent({
429430
)
430431
}
431432

432-
function WorkspaceResourceSubmenu({
433-
workspace,
434-
excludeTypes,
435-
onSelect,
436-
}: {
433+
interface WorkspaceResourceSubmenuProps {
437434
workspace: { id: string; name: string }
435+
/** Must be referentially stable (a module constant) — it keys the group memo. */
438436
excludeTypes?: readonly MothershipResourceType[]
437+
selectFolders?: boolean
439438
onSelect: (resource: MothershipResource) => void
440-
}) {
439+
}
440+
441+
/**
442+
* One workspace of an organization-wide picker: its own foldered resource menu,
443+
* fetched when the submenu first opens. Selections carry the workspace as owner.
444+
*/
445+
export function WorkspaceResourceSubmenu({
446+
workspace,
447+
excludeTypes,
448+
selectFolders,
449+
onSelect,
450+
}: WorkspaceResourceSubmenuProps) {
441451
const [open, setOpen] = useState(false)
442452
return (
443453
<DropdownMenuSub open={open} onOpenChange={setOpen}>
@@ -450,6 +460,7 @@ function WorkspaceResourceSubmenu({
450460
enabled={open}
451461
excludeTypes={excludeTypes}
452462
searchable={false}
463+
selectFolders={selectFolders}
453464
onSelect={onSelect}
454465
/>
455466
</DropdownMenuSubContent>

‎apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {
44
ResourceFolderTreeItems,
55
ResourceMenuSections,
66
useResourceTreeSections,
7+
WorkspaceResourceSubmenu,
78
} from './add-resource-dropdown'
89
export { useAvailableResources } from './available-resources'
910
export { resourceFromItem } from './resource-from-item'

‎apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/plus-menu-dropdown/plus-menu-dropdown.tsx‎

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
resourceFromItem,
1616
useAvailableResources,
1717
useResourceTreeSections,
18+
WorkspaceResourceSubmenu,
1819
} from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown'
1920
import type { AvailableResources } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/available-resources'
2021
import {
@@ -62,6 +63,22 @@ const MENTION_MAX_HEIGHT_CLASS = 'max-h-[min(280px,var(--radix-popper-available-
6263
* (`ADD_RESOURCE_EXCLUDED_TYPES` in `resource-tabs`).
6364
*/
6465
const MENTION_ONLY_RESOURCE_TYPES = new Set<MothershipResourceType>(['integration'])
66+
67+
/**
68+
* Families an organization chat's workspace submenus leave out: the mention-only
69+
* ones, plus Browser and Terminal, which belong to this desktop rather than to a
70+
* workspace and so sit once after the workspaces.
71+
*/
72+
const WORKSPACE_SUBMENU_EXCLUDED_TYPES: readonly MothershipResourceType[] = [
73+
...MENTION_ONLY_RESOURCE_TYPES,
74+
'browser',
75+
'terminal',
76+
]
77+
78+
function isNativeResourceGroup({ type }: { type: MothershipResourceType }): boolean {
79+
return type === 'browser' || type === 'terminal'
80+
}
81+
6582
const EMPTY_BROWSER_TABS = [] as const
6683
const EMPTY_TERMINAL_TABS = [] as const
6784

@@ -138,12 +155,9 @@ export const PlusMenuDropdown = React.memo(
138155
: workspaceInventory
139156
const { structureFolders } = combined
140157
const availableResources = organizationId
141-
? [
142-
...combined.groups,
143-
...workspaceInventory.groups.filter(
144-
(group) => group.type === 'browser' || group.type === 'terminal'
145-
),
146-
].sort(byResourceMenuOrder)
158+
? [...combined.groups, ...workspaceInventory.groups.filter(isNativeResourceGroup)].sort(
159+
byResourceMenuOrder
160+
)
147161
: combined.groups
148162
const isHydrating = combined.isHydrating || Boolean(organizationId && workspacesPending)
149163

@@ -183,9 +197,14 @@ export const PlusMenuDropdown = React.memo(
183197
terminalTabs,
184198
])
185199

200+
/**
201+
* Built from this workspace's own inventory, which has no foldered families in an
202+
* organization chat: there each workspace submenu builds its own sections, because
203+
* ids are only unique within a workspace.
204+
*/
186205
const treeSections = useResourceTreeSections({
187-
groups: availableResources,
188-
structureFolders,
206+
groups: workspaceInventory.groups,
207+
structureFolders: workspaceInventory.structureFolders,
189208
selectFolders: true,
190209
})
191210

@@ -386,10 +405,21 @@ export const PlusMenuDropdown = React.memo(
386405
{/* Always-mounted; swapping this subtree with filtered results makes Radix's
387406
menu FocusScope steal focus from the search input back to the content root. */}
388407
<div hidden={filteredItems !== null}>
408+
{organizationId &&
409+
workspaces.map((workspace) => (
410+
<WorkspaceResourceSubmenu
411+
key={workspace.id}
412+
workspace={workspace}
413+
excludeTypes={WORKSPACE_SUBMENU_EXCLUDED_TYPES}
414+
selectFolders
415+
onSelect={handleSelect}
416+
/>
417+
))}
389418
<ResourceMenuSections
390-
flat={Boolean(organizationId)}
391-
sections={organizationId ? [] : treeSections}
392-
groups={visibleResources}
419+
sections={treeSections}
420+
groups={
421+
organizationId ? visibleResources.filter(isNativeResourceGroup) : visibleResources
422+
}
393423
onSelect={handleSelect}
394424
subContentClassName='max-w-[min(300px,calc(100vw-32px))]'
395425
/>

0 commit comments

Comments
 (0)