mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement(resource): sorting and icons
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { Fragment } from 'react'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn'
|
||||
import { Button, Plus } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { ArrowUpDown, ListFilter, Search } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn'
|
||||
import { ArrowUpDown, Button, ListFilter, Search } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
|
||||
interface ResourceOptionsBarProps {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { Skeleton } from '@/components/emcn'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { ArrowDown, ArrowUp, Button, Plus, Skeleton } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { ResourceHeader } from './components/resource-header'
|
||||
import { ResourceOptionsBar } from './components/resource-options-bar'
|
||||
@@ -36,6 +35,7 @@ interface ResourceProps {
|
||||
onChange: (value: string) => void
|
||||
placeholder?: string
|
||||
}
|
||||
defaultSort: string
|
||||
onSort?: () => void
|
||||
onFilter?: () => void
|
||||
toolbarActions?: ReactNode
|
||||
@@ -59,6 +59,7 @@ export function Resource({
|
||||
title,
|
||||
create,
|
||||
search,
|
||||
defaultSort,
|
||||
onSort,
|
||||
onFilter,
|
||||
toolbarActions,
|
||||
@@ -71,6 +72,10 @@ export function Resource({
|
||||
onContextMenu,
|
||||
}: ResourceProps) {
|
||||
const headerRef = useRef<HTMLDivElement>(null)
|
||||
const [sort, setSort] = useState<{ column: string; direction: 'asc' | 'desc' }>({
|
||||
column: defaultSort,
|
||||
direction: 'desc',
|
||||
})
|
||||
|
||||
const handleBodyScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
||||
if (headerRef.current) {
|
||||
@@ -78,6 +83,22 @@ export function Resource({
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleColumnSort = useCallback((columnId: string) => {
|
||||
setSort((prev) => {
|
||||
if (prev.column !== columnId) return { column: columnId, direction: 'asc' }
|
||||
return { column: columnId, direction: prev.direction === 'asc' ? 'desc' : 'asc' }
|
||||
})
|
||||
}, [])
|
||||
|
||||
const sortedRows = useMemo(() => {
|
||||
return [...rows].sort((a, b) => {
|
||||
const aLabel = a.cells[sort.column]?.label ?? ''
|
||||
const bLabel = b.cells[sort.column]?.label ?? ''
|
||||
const cmp = aLabel.localeCompare(bLabel)
|
||||
return sort.direction === 'asc' ? -cmp : cmp
|
||||
})
|
||||
}, [rows, sort])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='flex h-full flex-1 flex-col overflow-hidden bg-white dark:bg-[var(--bg)]'
|
||||
@@ -100,14 +121,23 @@ export function Resource({
|
||||
<ResourceColGroup columns={columns} />
|
||||
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th
|
||||
key={col.id}
|
||||
className='h-10 px-[24px] py-[10px] text-left align-middle font-base text-[var(--text-muted)]'
|
||||
>
|
||||
{col.header}
|
||||
</th>
|
||||
))}
|
||||
{columns.map((col) => {
|
||||
const SortIcon = sort.direction === 'asc' ? ArrowUp : ArrowDown
|
||||
return (
|
||||
<th key={col.id} className='h-10 px-[16px] py-[6px] text-left align-middle'>
|
||||
<Button
|
||||
variant='subtle'
|
||||
className='px-[8px] py-[4px] font-base text-[var(--text-muted)] hover:text-[var(--text-muted)]'
|
||||
onClick={() => handleColumnSort(col.id)}
|
||||
>
|
||||
{col.header}
|
||||
{sort.column === col.id && (
|
||||
<SortIcon className='ml-[4px] h-[12px] w-[12px]' />
|
||||
)}
|
||||
</Button>
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
@@ -116,7 +146,7 @@ export function Resource({
|
||||
<table className='w-full table-fixed text-[13px]'>
|
||||
<ResourceColGroup columns={columns} />
|
||||
<tbody>
|
||||
{rows.map((row) => (
|
||||
{sortedRows.map((row) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
data-resource-row
|
||||
|
||||
@@ -206,6 +206,7 @@ export function Files() {
|
||||
onChange: setSearchTerm,
|
||||
placeholder: 'Search files...',
|
||||
}}
|
||||
defaultSort='created'
|
||||
onSort={() => {}}
|
||||
onFilter={() => {}}
|
||||
columns={COLUMNS}
|
||||
|
||||
@@ -201,6 +201,7 @@ export function Knowledge() {
|
||||
onChange: setSearchQuery,
|
||||
placeholder: 'Search knowledge bases...',
|
||||
}}
|
||||
defaultSort='created'
|
||||
onSort={handleSort}
|
||||
onFilter={handleFilter}
|
||||
columns={COLUMNS}
|
||||
|
||||
@@ -107,6 +107,7 @@ export function Schedules() {
|
||||
onChange: setSearchQuery,
|
||||
placeholder: 'Search schedules...',
|
||||
}}
|
||||
defaultSort='nextRun'
|
||||
onSort={() => {}}
|
||||
onFilter={() => {}}
|
||||
columns={COLUMNS}
|
||||
|
||||
@@ -156,6 +156,7 @@ export function Tables() {
|
||||
onChange: setSearchTerm,
|
||||
placeholder: 'Search tables...',
|
||||
}}
|
||||
defaultSort='created'
|
||||
onSort={handleSort}
|
||||
onFilter={handleFilter}
|
||||
columns={COLUMNS}
|
||||
|
||||
25
apps/sim/components/emcn/icons/arrow-down.tsx
Normal file
25
apps/sim/components/emcn/icons/arrow-down.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
/**
|
||||
* ArrowDown icon component
|
||||
* @param props - SVG properties including className, fill, etc.
|
||||
*/
|
||||
export function ArrowDown(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path d='M4 11.25L10.25 17.5L16.5 11.25' />
|
||||
<path d='M10.25 3V17.5' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
27
apps/sim/components/emcn/icons/arrow-up-down.tsx
Normal file
27
apps/sim/components/emcn/icons/arrow-up-down.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
/**
|
||||
* ArrowUpDown icon component for sort toggles
|
||||
* @param props - SVG properties including className, fill, etc.
|
||||
*/
|
||||
export function ArrowUpDown(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path d='M1.5 8L5.5 4L9.5 8' />
|
||||
<path d='M5.5 4V16.5' />
|
||||
<path d='M11 12.5L15 16.5L19 12.5' />
|
||||
<path d='M15 4V16.5' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
25
apps/sim/components/emcn/icons/arrow-up.tsx
Normal file
25
apps/sim/components/emcn/icons/arrow-up.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
/**
|
||||
* ArrowUp icon component
|
||||
* @param props - SVG properties including className, fill, etc.
|
||||
*/
|
||||
export function ArrowUp(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path d='M4 9.25L10.25 3L16.5 9.25' />
|
||||
<path d='M10.25 3V17.5' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
export { ArrowDown } from './arrow-down'
|
||||
export { ArrowUp } from './arrow-up'
|
||||
export { ArrowUpDown } from './arrow-up-down'
|
||||
export { Blimp } from './blimp'
|
||||
export { BubbleChatClose } from './bubble-chat-close'
|
||||
export { BubbleChatPreview } from './bubble-chat-preview'
|
||||
@@ -21,14 +24,17 @@ export { Home } from './home'
|
||||
export { Key } from './key'
|
||||
export { Layout } from './layout'
|
||||
export { Library } from './library'
|
||||
export { ListFilter } from './list-filter'
|
||||
export { Loader } from './loader'
|
||||
export { MoreHorizontal } from './more-horizontal'
|
||||
export { NoWrap } from './no-wrap'
|
||||
export { PanelLeft } from './panel-left'
|
||||
export { Play, PlayOutline } from './play'
|
||||
export { Plus } from './plus'
|
||||
export { Redo } from './redo'
|
||||
export { Rocket } from './rocket'
|
||||
export { Rows3 } from './rows3'
|
||||
export { Search } from './search'
|
||||
export { Table } from './table'
|
||||
export { TerminalWindow } from './terminal-window'
|
||||
export { Trash } from './trash'
|
||||
|
||||
26
apps/sim/components/emcn/icons/list-filter.tsx
Normal file
26
apps/sim/components/emcn/icons/list-filter.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
/**
|
||||
* ListFilter icon component for filter controls
|
||||
* @param props - SVG properties including className, fill, etc.
|
||||
*/
|
||||
export function ListFilter(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path d='M1.5 4.5H19' />
|
||||
<path d='M5 10.25H15.5' />
|
||||
<path d='M8.25 16H12.25' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
25
apps/sim/components/emcn/icons/plus.tsx
Normal file
25
apps/sim/components/emcn/icons/plus.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
/**
|
||||
* Plus icon component
|
||||
* @param props - SVG properties including className, fill, etc.
|
||||
*/
|
||||
export function Plus(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path d='M10.25 3V17.5' />
|
||||
<path d='M3 10.25H17.5' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
25
apps/sim/components/emcn/icons/search.tsx
Normal file
25
apps/sim/components/emcn/icons/search.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
/**
|
||||
* Search icon component (magnifying glass)
|
||||
* @param props - SVG properties including className, fill, etc.
|
||||
*/
|
||||
export function Search(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<circle cx='8.5' cy='8.5' r='6.5' />
|
||||
<path d='M13.5 13.5L18.5 18.5' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -37,12 +37,8 @@ async function listDirectory(
|
||||
dirPath: string,
|
||||
retryOptions?: Parameters<typeof fetchWithRetry>[2]
|
||||
): Promise<string[]> {
|
||||
const encodedDir = dirPath
|
||||
? dirPath.split('/').map(encodeURIComponent).join('/')
|
||||
: ''
|
||||
const endpoint = encodedDir
|
||||
? `${baseUrl}/vault/${encodedDir}/`
|
||||
: `${baseUrl}/vault/`
|
||||
const encodedDir = dirPath ? dirPath.split('/').map(encodeURIComponent).join('/') : ''
|
||||
const endpoint = encodedDir ? `${baseUrl}/vault/${encodedDir}/` : `${baseUrl}/vault/`
|
||||
|
||||
const response = await fetchWithRetry(
|
||||
endpoint,
|
||||
|
||||
Reference in New Issue
Block a user