Skip to content

Commit 98cf4d3

Browse files
committed
feat(search): show folder path for files in cmd-k modal, strip extraneous comments
- FileItem interface with folderPath?: string[] added to search modal utils - MemoizedFileItem component renders folder breadcrumb identically to MemoizedWorkflowItem — truncated path segments on the right with / separators - FilesGroup rewritten as a dedicated memo component (was createIconGroup factory) so it accepts FileItem[] and includes folderPath segments in the search value - searchModalFiles in sidebar splits f.folderPath string into string[] segments - search-modal.tsx typed to FileItem and includes folderPath in filterAndSort - Remove self-explanatory "Phase 1" section label from download route - Remove redundant TSDoc on the unique index in db schema
1 parent 9525ac0 commit 98cf4d3

7 files changed

Lines changed: 97 additions & 11 deletions

File tree

apps/sim/app/api/workspaces/[id]/files/download/route.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,9 @@ export const GET = withRouteHandler(
111111
)
112112
}
113113

114-
// Phase 1: fetch all buffers in parallel
115114
const buffers = await Promise.all(filesToZip.map((file) => fetchWorkspaceFileBuffer(file)))
116115

117-
// Phase 2: assemble zip synchronously so path deduplication is deterministic
116+
// Assemble zip synchronously so path deduplication is deterministic.
118117
const zip = new JSZip()
119118
const usedPaths = new Set<string>()
120119
for (let i = 0; i < filesToZip.length; i++) {

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ComponentType } from 'react'
44
import { memo } from 'react'
55
import { Command } from 'cmdk'
66
import { Blimp } from '@/components/emcn'
7+
import { File } from '@/components/emcn/icons'
78
import { cn } from '@/lib/core/utils/cn'
89
import { workflowBorderColor } from '@/lib/workspaces/colors'
910
import type { CommandItemProps } from '../utils'
@@ -101,6 +102,50 @@ export const MemoizedWorkflowItem = memo(
101102
(prev.folderPath ?? []).every((segment, i) => segment === next.folderPath?.[i])))
102103
)
103104

105+
export const MemoizedFileItem = memo(
106+
function FileItem({
107+
value,
108+
onSelect,
109+
name,
110+
folderPath,
111+
}: {
112+
value: string
113+
onSelect: () => void
114+
name: string
115+
folderPath?: string[]
116+
}) {
117+
return (
118+
<Command.Item value={value} onSelect={onSelect} className={COMMAND_ITEM_CLASSNAME}>
119+
<div className='relative flex size-[16px] flex-shrink-0 items-center justify-center'>
120+
<File className='size-[14px] text-[var(--text-icon)]' />
121+
</div>
122+
<span className='flex min-w-0 max-w-[75%] flex-shrink-0 font-base text-[var(--text-body)]'>
123+
<span className='truncate'>{name}</span>
124+
</span>
125+
{folderPath && folderPath.length > 0 && (
126+
<span className='ml-auto flex min-w-0 pl-2 font-base text-[var(--text-subtle)] text-small'>
127+
{folderPath.length > 1 && (
128+
<>
129+
<span className='min-w-0 truncate [flex-shrink:9999]'>
130+
{folderPath.slice(0, -1).join(' / ')}
131+
</span>
132+
<span className='flex-shrink-0 whitespace-pre'> / </span>
133+
</>
134+
)}
135+
<span className='min-w-0 truncate'>{folderPath[folderPath.length - 1]}</span>
136+
</span>
137+
)}
138+
</Command.Item>
139+
)
140+
},
141+
(prev, next) =>
142+
prev.value === next.value &&
143+
prev.name === next.name &&
144+
(prev.folderPath === next.folderPath ||
145+
(prev.folderPath?.length === next.folderPath?.length &&
146+
(prev.folderPath ?? []).every((segment, i) => segment === next.folderPath?.[i])))
147+
)
148+
104149
export const MemoizedTaskItem = memo(
105150
function TaskItem({
106151
value,

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
import type { ComponentType } from 'react'
44
import { memo } from 'react'
55
import { Command } from 'cmdk'
6-
import { Database, File, Table } from '@/components/emcn/icons'
6+
import { Database, Table } from '@/components/emcn/icons'
77
import type {
88
SearchBlockItem,
99
SearchDocItem,
1010
SearchToolOperationItem,
1111
} from '@/stores/modals/search/types'
12-
import type { PageItem, TaskItem, WorkflowItem, WorkspaceItem } from '../utils'
12+
import type { FileItem, PageItem, TaskItem, WorkflowItem, WorkspaceItem } from '../utils'
1313
import { GROUP_HEADING_CLASSNAME } from '../utils'
1414
import {
1515
MemoizedCommandItem,
16+
MemoizedFileItem,
1617
MemoizedIconItem,
1718
MemoizedPageItem,
1819
MemoizedTaskItem,
@@ -245,9 +246,31 @@ export const PagesGroup = memo(function PagesGroup({
245246
})
246247

247248
export const TablesGroup = createIconGroup('Tables', 'table', Table)
248-
export const FilesGroup = createIconGroup('Files', 'file', File)
249249
export const KnowledgeBasesGroup = createIconGroup('Knowledge Bases', 'knowledge-base', Database)
250250

251+
export const FilesGroup = memo(function FilesGroup({
252+
items,
253+
onSelect,
254+
}: {
255+
items: FileItem[]
256+
onSelect: (file: FileItem) => void
257+
}) {
258+
if (items.length === 0) return null
259+
return (
260+
<Command.Group heading='Files' className={GROUP_HEADING_CLASSNAME}>
261+
{items.map((file) => (
262+
<MemoizedFileItem
263+
key={file.id}
264+
value={`${file.name} ${file.folderPath?.join(' / ') ?? ''} file-${file.id}`}
265+
onSelect={() => onSelect(file)}
266+
name={file.name}
267+
folderPath={file.folderPath}
268+
/>
269+
))}
270+
</Command.Group>
271+
)
272+
})
273+
251274
function createIconGroup(
252275
heading: string,
253276
prefix: string,

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,21 @@ import {
3434
WorkflowsGroup,
3535
WorkspacesGroup,
3636
} from './components/search-groups'
37-
import type { PageItem, SearchModalProps, TaskItem, WorkflowItem, WorkspaceItem } from './utils'
37+
import type {
38+
FileItem,
39+
PageItem,
40+
SearchModalProps,
41+
TaskItem,
42+
WorkflowItem,
43+
WorkspaceItem,
44+
} from './utils'
3845
import { filterAndSort } from './utils'
3946

4047
const EMPTY_WORKFLOWS: WorkflowItem[] = []
4148
const EMPTY_WORKSPACES: WorkspaceItem[] = []
4249
const EMPTY_TASKS: TaskItem[] = []
4350
const EMPTY_TABLES: TaskItem[] = []
44-
const EMPTY_FILES: TaskItem[] = []
51+
const EMPTY_FILES: FileItem[] = []
4552
const EMPTY_KNOWLEDGE_BASES: TaskItem[] = []
4653

4754
export type { SearchModalProps } from './utils'
@@ -289,7 +296,7 @@ export function SearchModal({
289296
)
290297

291298
const handleFileSelect = useCallback(
292-
(item: TaskItem) => {
299+
(item: FileItem) => {
293300
routerRef.current.push(item.href)
294301
captureEvent(posthogRef.current, 'search_result_selected', {
295302
result_type: 'file',
@@ -401,7 +408,12 @@ export function SearchModal({
401408
[tables, deferredSearch]
402409
)
403410
const filteredFiles = useMemo(
404-
() => filterAndSort(files, (f) => `${f.name} file-${f.id}`, deferredSearch),
411+
() =>
412+
filterAndSort(
413+
files,
414+
(f) => `${f.name} ${f.folderPath?.join(' / ') ?? ''} file-${f.id}`,
415+
deferredSearch
416+
),
405417
[files, deferredSearch]
406418
)
407419
const filteredKnowledgeBases = useMemo(

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,21 @@ export interface PageItem {
3232
hidden?: boolean
3333
}
3434

35+
export interface FileItem {
36+
id: string
37+
name: string
38+
href: string
39+
folderPath?: string[]
40+
}
41+
3542
export interface SearchModalProps {
3643
open: boolean
3744
onOpenChange: (open: boolean) => void
3845
workflows?: WorkflowItem[]
3946
workspaces?: WorkspaceItem[]
4047
tasks?: TaskItem[]
4148
tables?: TaskItem[]
42-
files?: TaskItem[]
49+
files?: FileItem[]
4350
knowledgeBases?: TaskItem[]
4451
isOnWorkflowPage?: boolean
4552
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ export const Sidebar = memo(function Sidebar() {
826826
id: f.id,
827827
name: f.name,
828828
href: `/workspace/${workspaceId}/files/${f.id}`,
829+
folderPath: f.folderPath ? f.folderPath.split('/') : undefined,
829830
})),
830831
[fetchedFiles, workspaceId, permissionConfig.hideFilesTab]
831832
)

packages/db/schema.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1243,7 +1243,6 @@ export const workspaceFiles = pgTable(
12431243
keyActiveUniqueIdx: uniqueIndex('workspace_files_key_active_unique')
12441244
.on(table.key)
12451245
.where(sql`${table.deletedAt} IS NULL`),
1246-
/** One active display name per workspace file folder. */
12471246
workspaceFolderOriginalNameActiveUnique: uniqueIndex(
12481247
'workspace_files_workspace_folder_name_active_unique'
12491248
)

0 commit comments

Comments
 (0)