Files
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

303 lines
10 KiB
TypeScript

import { createLogger } from '@sim/logger'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
const logger = createLogger('FoldersStore')
interface FolderState {
expandedFolders: Set<string>
selectedWorkflows: Set<string>
selectedFolders: Set<string>
lastSelectedFolderId: string | null
selectedChats: Set<string>
lastSelectedChatId: string | null
toggleExpanded: (folderId: string) => void
setExpanded: (folderId: string, expanded: boolean) => void
// Workflow selection actions
selectWorkflow: (workflowId: string) => void
deselectWorkflow: (workflowId: string) => void
toggleWorkflowSelection: (workflowId: string) => void
clearSelection: () => void
selectOnly: (workflowId: string) => void
selectRange: (workflowIds: string[], fromId: string, toId: string) => void
isWorkflowSelected: (workflowId: string) => boolean
// Folder selection actions
selectFolder: (folderId: string) => void
deselectFolder: (folderId: string) => void
toggleFolderSelection: (folderId: string) => void
clearFolderSelection: () => void
selectFolderOnly: (folderId: string) => void
selectFolderRange: (folderIds: string[], fromId: string, toId: string) => void
isFolderSelected: (folderId: string) => boolean
// Chat selection actions
selectChatOnly: (chatId: string) => void
toggleChatSelection: (chatId: string) => void
selectChatRange: (chatIds: string[], fromId: string, toId: string) => void
clearChatSelection: () => void
isChatSelected: (chatId: string) => boolean
// Unified selection helpers
getFullSelection: () => { workflowIds: string[]; folderIds: string[]; chatIds: string[] }
hasAnySelection: () => boolean
isMixedSelection: () => boolean
clearAllSelection: () => void
}
export const useFolderStore = create<FolderState>()(
devtools(
(set, get) => ({
expandedFolders: new Set(),
selectedWorkflows: new Set(),
selectedFolders: new Set(),
lastSelectedFolderId: null,
selectedChats: new Set(),
lastSelectedChatId: null,
toggleExpanded: (folderId) =>
set((state) => {
const newExpanded = new Set(state.expandedFolders)
if (newExpanded.has(folderId)) {
newExpanded.delete(folderId)
} else {
newExpanded.add(folderId)
}
return { expandedFolders: newExpanded }
}),
setExpanded: (folderId, expanded) =>
set((state) => {
const newExpanded = new Set(state.expandedFolders)
if (expanded) {
newExpanded.add(folderId)
} else {
newExpanded.delete(folderId)
}
return { expandedFolders: newExpanded }
}),
// Selection actions
selectWorkflow: (workflowId) =>
set((state) => {
const newSelected = new Set(state.selectedWorkflows)
newSelected.add(workflowId)
return { selectedWorkflows: newSelected }
}),
deselectWorkflow: (workflowId) =>
set((state) => {
const newSelected = new Set(state.selectedWorkflows)
newSelected.delete(workflowId)
return { selectedWorkflows: newSelected }
}),
toggleWorkflowSelection: (workflowId) =>
set((state) => {
const newSelected = new Set(state.selectedWorkflows)
if (newSelected.has(workflowId)) {
newSelected.delete(workflowId)
} else {
newSelected.add(workflowId)
}
return {
selectedWorkflows: newSelected,
...(state.selectedChats.size > 0 && {
selectedChats: new Set<string>(),
lastSelectedChatId: null,
}),
}
}),
clearSelection: () => set({ selectedWorkflows: new Set() }),
selectOnly: (workflowId) =>
set({
selectedWorkflows: new Set([workflowId]),
selectedFolders: new Set(),
lastSelectedFolderId: null,
selectedChats: new Set(),
lastSelectedChatId: null,
}),
selectRange: (workflowIds, fromId, toId) => {
const fromIndex = workflowIds.indexOf(fromId)
const toIndex = workflowIds.indexOf(toId)
if (fromIndex === -1 || toIndex === -1) return
const [start, end] = fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex]
const rangeIds = workflowIds.slice(start, end + 1)
set({ selectedWorkflows: new Set(rangeIds) })
},
isWorkflowSelected: (workflowId) => get().selectedWorkflows.has(workflowId),
// Folder selection actions
selectFolder: (folderId) =>
set((state) => {
const newSelected = new Set(state.selectedFolders)
newSelected.add(folderId)
return { selectedFolders: newSelected, lastSelectedFolderId: folderId }
}),
deselectFolder: (folderId) =>
set((state) => {
const newSelected = new Set(state.selectedFolders)
newSelected.delete(folderId)
// If deselecting the last selected folder, update anchor to another selected folder or null
const newLastSelected =
state.lastSelectedFolderId === folderId
? (Array.from(newSelected)[0] ?? null)
: state.lastSelectedFolderId
return { selectedFolders: newSelected, lastSelectedFolderId: newLastSelected }
}),
toggleFolderSelection: (folderId) =>
set((state) => {
const newSelected = new Set(state.selectedFolders)
let newLastSelected: string | null
if (newSelected.has(folderId)) {
newSelected.delete(folderId)
// If toggling off the last selected, pick another or null
newLastSelected =
state.lastSelectedFolderId === folderId
? (Array.from(newSelected)[0] ?? null)
: state.lastSelectedFolderId
} else {
newSelected.add(folderId)
// Always update anchor to the most recently clicked folder
newLastSelected = folderId
}
return {
selectedFolders: newSelected,
lastSelectedFolderId: newLastSelected,
...(state.selectedChats.size > 0 && {
selectedChats: new Set<string>(),
lastSelectedChatId: null,
}),
}
}),
clearFolderSelection: () => set({ selectedFolders: new Set(), lastSelectedFolderId: null }),
selectFolderOnly: (folderId) =>
set({
selectedFolders: new Set([folderId]),
lastSelectedFolderId: folderId,
selectedChats: new Set(),
lastSelectedChatId: null,
}),
selectFolderRange: (folderIds, fromId, toId) => {
const fromIndex = folderIds.indexOf(fromId)
const toIndex = folderIds.indexOf(toId)
if (fromIndex === -1 || toIndex === -1) return
const [start, end] = fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex]
const rangeIds = folderIds.slice(start, end + 1)
set({ selectedFolders: new Set(rangeIds), lastSelectedFolderId: fromId })
},
isFolderSelected: (folderId) => get().selectedFolders.has(folderId),
// Chat selection actions
selectChatOnly: (chatId) =>
set((state) => ({
selectedChats: new Set([chatId]),
lastSelectedChatId: chatId,
...(state.selectedWorkflows.size > 0 && { selectedWorkflows: new Set<string>() }),
...(state.selectedFolders.size > 0 && {
selectedFolders: new Set<string>(),
lastSelectedFolderId: null,
}),
})),
toggleChatSelection: (chatId) =>
set((state) => {
const newSelected = new Set(state.selectedChats)
let newLastSelected: string | null
if (newSelected.has(chatId)) {
newSelected.delete(chatId)
newLastSelected =
state.lastSelectedChatId === chatId
? (Array.from(newSelected)[0] ?? null)
: state.lastSelectedChatId
} else {
newSelected.add(chatId)
newLastSelected = chatId
}
return {
selectedChats: newSelected,
lastSelectedChatId: newLastSelected,
...(state.selectedWorkflows.size > 0 && { selectedWorkflows: new Set<string>() }),
...(state.selectedFolders.size > 0 && {
selectedFolders: new Set<string>(),
lastSelectedFolderId: null,
}),
}
}),
selectChatRange: (chatIds, fromId, toId) => {
const fromIndex = chatIds.indexOf(fromId)
const toIndex = chatIds.indexOf(toId)
if (fromIndex === -1 || toIndex === -1) return
const [start, end] = fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex]
const rangeIds = chatIds.slice(start, end + 1)
const state = get()
set({
selectedChats: new Set(rangeIds),
lastSelectedChatId: fromId,
...(state.selectedWorkflows.size > 0 && { selectedWorkflows: new Set<string>() }),
...(state.selectedFolders.size > 0 && {
selectedFolders: new Set<string>(),
lastSelectedFolderId: null,
}),
})
},
clearChatSelection: () => set({ selectedChats: new Set(), lastSelectedChatId: null }),
isChatSelected: (chatId) => get().selectedChats.has(chatId),
// Unified selection helpers
getFullSelection: () => ({
workflowIds: Array.from(get().selectedWorkflows),
folderIds: Array.from(get().selectedFolders),
chatIds: Array.from(get().selectedChats),
}),
hasAnySelection: () =>
get().selectedWorkflows.size > 0 ||
get().selectedFolders.size > 0 ||
get().selectedChats.size > 0,
isMixedSelection: () => get().selectedWorkflows.size > 0 && get().selectedFolders.size > 0,
clearAllSelection: () =>
set({
selectedWorkflows: new Set(),
selectedFolders: new Set(),
lastSelectedFolderId: null,
selectedChats: new Set(),
lastSelectedChatId: null,
}),
}),
{ name: 'folder-store' }
)
)
export const useIsWorkflowSelected = (workflowId: string) =>
useFolderStore((state) => state.selectedWorkflows.has(workflowId))
export const useIsFolderSelected = (folderId: string) =>
useFolderStore((state) => state.selectedFolders.has(folderId))