chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
||||
'use client'
|
||||
|
||||
import { useId, useMemo, useState } from 'react'
|
||||
import { Checkbox, ChevronDown, cn } from '@sim/emcn'
|
||||
import {
|
||||
ForkFileTree,
|
||||
type ForkFlatFile,
|
||||
groupForkFilesIntoFolders,
|
||||
} from '@/ee/workspace-forking/components/fork-file-tree/fork-file-tree'
|
||||
|
||||
/** A flat copyable resource (table / KB / custom tool / skill / MCP server) in the picker. */
|
||||
export interface ForkResourcePickerItem {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface ResourceKindRowProps {
|
||||
label: string
|
||||
items: ForkResourcePickerItem[]
|
||||
selected: Set<string>
|
||||
/** Toggle the given ids on/off. Used by the select-all header checkbox. */
|
||||
onToggleMany: (ids: string[], checked: boolean) => void
|
||||
onToggleItem: (id: string, checked: boolean) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* One expandable resource kind in the fork / sync copy picker: a tri-state "select all" header
|
||||
* (count of selected / total) plus, when expanded, a scrollable list of individual resources so
|
||||
* the user can copy a specific subset. Shared by the fork modal's "Copy resources" and the sync
|
||||
* modal's "Copy resources" so the two surfaces stay identical. Files nest in a folder tree
|
||||
* instead - use {@link FileKindRow}.
|
||||
*
|
||||
* Expanded body uses the settings MCP expand pattern (`border-t` + `--surface-2`) so items
|
||||
* read as contained in the kind rather than indented siblings of other kinds.
|
||||
*/
|
||||
export function ResourceKindRow({
|
||||
label,
|
||||
items,
|
||||
selected,
|
||||
onToggleMany,
|
||||
onToggleItem,
|
||||
disabled = false,
|
||||
}: ResourceKindRowProps) {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const fieldId = useId()
|
||||
|
||||
const total = items.length
|
||||
const selectedCount = items.reduce((count, item) => count + (selected.has(item.id) ? 1 : 0), 0)
|
||||
const headerState = selectedCount === 0 ? false : selectedCount === total ? true : 'indeterminate'
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex items-center gap-2 text-[var(--text-body)] text-sm'>
|
||||
<Checkbox
|
||||
size='sm'
|
||||
aria-label={`Copy all ${label}`}
|
||||
checked={headerState}
|
||||
onCheckedChange={() =>
|
||||
onToggleMany(
|
||||
items.map((item) => item.id),
|
||||
headerState !== true
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<button
|
||||
type='button'
|
||||
className='flex min-w-0 flex-1 items-center gap-1 text-left hover:text-[var(--text-primary)]'
|
||||
onClick={() => setExpanded((value) => !value)}
|
||||
>
|
||||
<span className='min-w-0 flex-1 truncate'>
|
||||
{label} ({selectedCount > 0 ? `${selectedCount}/${total}` : total})
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-[6px] w-[10px] flex-shrink-0 text-[var(--text-icon)] transition-transform',
|
||||
expanded && 'rotate-180'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{expanded ? (
|
||||
<div className='mt-1 max-h-44 overflow-y-auto border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 py-2'>
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
{items.map((item) => {
|
||||
const isChecked = selected.has(item.id)
|
||||
const itemId = `${fieldId}-${item.id}`
|
||||
return (
|
||||
<label
|
||||
key={item.id}
|
||||
htmlFor={itemId}
|
||||
className={cn(
|
||||
'flex min-w-0 items-center gap-2 rounded-md py-0.5 text-[var(--text-body)] text-sm',
|
||||
disabled
|
||||
? 'cursor-not-allowed opacity-60'
|
||||
: 'cursor-pointer hover:text-[var(--text-primary)]'
|
||||
)}
|
||||
>
|
||||
<Checkbox
|
||||
id={itemId}
|
||||
size='sm'
|
||||
checked={isChecked}
|
||||
onCheckedChange={(checked) => onToggleItem(item.id, checked === true)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span className='truncate'>{item.label}</span>
|
||||
</label>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface FileKindRowProps {
|
||||
label: string
|
||||
files: ForkFlatFile[]
|
||||
selected: Set<string>
|
||||
onToggleAll: (selectAll: boolean) => void
|
||||
onToggleItem: (id: string, checked: boolean) => void
|
||||
onToggleMany: (ids: string[], checked: boolean) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The Files kind: a tri-state "select all" header (count selected / total) that expands to a
|
||||
* folder ▸ file tree, so the user can copy a whole folder or a specific file. Files are the only
|
||||
* copyable kind that nests; every other kind uses the flat {@link ResourceKindRow}. Shared by the
|
||||
* fork and sync copy pickers so both group files identically.
|
||||
*/
|
||||
export function FileKindRow({
|
||||
label,
|
||||
files,
|
||||
selected,
|
||||
onToggleAll,
|
||||
onToggleItem,
|
||||
onToggleMany,
|
||||
disabled = false,
|
||||
}: FileKindRowProps) {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
const total = files.length
|
||||
const selectedCount = files.filter((file) => selected.has(file.id)).length
|
||||
const headerState = selectedCount === 0 ? false : selectedCount === total ? true : 'indeterminate'
|
||||
|
||||
const { folders, rootFiles } = useMemo(() => groupForkFilesIntoFolders(files), [files])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex items-center gap-2 text-[var(--text-body)] text-sm'>
|
||||
<Checkbox
|
||||
size='sm'
|
||||
aria-label={`Copy all ${label}`}
|
||||
checked={headerState}
|
||||
onCheckedChange={() => onToggleAll(headerState !== true)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<button
|
||||
type='button'
|
||||
className='flex min-w-0 flex-1 items-center gap-1 text-left hover:text-[var(--text-primary)]'
|
||||
onClick={() => setExpanded((value) => !value)}
|
||||
>
|
||||
<span className='min-w-0 flex-1 truncate'>
|
||||
{label} ({selectedCount > 0 ? `${selectedCount}/${total}` : total})
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-[6px] w-[10px] flex-shrink-0 text-[var(--text-icon)] transition-transform',
|
||||
expanded && 'rotate-180'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{expanded ? (
|
||||
<div className='mt-1 border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 py-2'>
|
||||
<ForkFileTree
|
||||
folders={folders}
|
||||
rootFiles={rootFiles}
|
||||
isSelected={(id) => selected.has(id)}
|
||||
onToggleFile={onToggleItem}
|
||||
onToggleMany={onToggleMany}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user