From 2a8401688ca59ccb5f3942ef87440527e7309e24 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Fri, 27 Feb 2026 08:41:52 -0800 Subject: [PATCH] improvement(selectors): consolidate selector input logic --- .../document-selector/document-selector.tsx | 118 ---------- .../document-tag-entry/document-tag-entry.tsx | 40 +--- .../file-selector/file-selector-input.tsx | 209 ------------------ .../components/folder-selector-input.tsx | 124 ----------- .../components/sub-block/components/index.ts | 7 +- .../knowledge-tag-filters.tsx | 40 +--- .../project-selector-input.tsx | 140 ------------ .../selector-input/selector-input.tsx | 106 +++++++++ .../sheet-selector/sheet-selector-input.tsx | 136 ------------ .../slack-selector/slack-selector-input.tsx | 148 ------------- .../sub-block/hooks/use-depends-on-gate.ts | 2 + .../sub-block/hooks/use-selector-setup.ts | 81 +++++++ .../editor/components/sub-block/sub-block.tsx | 59 +++-- apps/sim/blocks/blocks/knowledge.ts | 2 + 14 files changed, 232 insertions(+), 980 deletions(-) delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-selector/document-selector.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/folder-selector/components/folder-selector-input.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/project-selector/project-selector-input.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-input/selector-input.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sheet-selector/sheet-selector-input.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-selector/slack-selector-input.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-selector-setup.ts diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-selector/document-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-selector/document-selector.tsx deleted file mode 100644 index 49a6f903d5..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-selector/document-selector.tsx +++ /dev/null @@ -1,118 +0,0 @@ -'use client' - -import { useCallback, useMemo } from 'react' -import { Tooltip } from '@/components/emcn' -import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' -import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox' -import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' -import { getBlock } from '@/blocks/registry' -import type { SubBlockConfig } from '@/blocks/types' -import type { SelectorContext } from '@/hooks/selectors/types' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' - -interface DocumentSelectorProps { - blockId: string - subBlock: SubBlockConfig - disabled?: boolean - onDocumentSelect?: (documentId: string) => void - isPreview?: boolean - previewValue?: string | null - previewContextValues?: Record -} - -export function DocumentSelector({ - blockId, - subBlock, - disabled = false, - onDocumentSelect, - isPreview = false, - previewValue, - previewContextValues, -}: DocumentSelectorProps) { - const { activeWorkflowId } = useWorkflowRegistry() - - const { finalDisabled } = useDependsOnGate(blockId, subBlock, { - disabled, - isPreview, - previewContextValues, - }) - - const blockState = useWorkflowStore((state) => state.blocks[blockId]) - const blockConfig = blockState?.type ? getBlock(blockState.type) : null - const canonicalIndex = useMemo( - () => buildCanonicalIndex(blockConfig?.subBlocks || []), - [blockConfig?.subBlocks] - ) - const canonicalModeOverrides = blockState?.data?.canonicalModes - - const blockValues = useSubBlockStore((state) => { - if (!activeWorkflowId) return {} - const workflowValues = state.workflowValues[activeWorkflowId] || {} - return (workflowValues as Record>)[blockId] || {} - }) - - const knowledgeBaseIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.knowledgeBaseId) - : resolveDependencyValue( - 'knowledgeBaseId', - blockValues, - canonicalIndex, - canonicalModeOverrides - ), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const normalizedKnowledgeBaseId = - typeof knowledgeBaseIdValue === 'string' && knowledgeBaseIdValue.trim().length > 0 - ? knowledgeBaseIdValue - : null - - const selectorContext = useMemo( - () => ({ - knowledgeBaseId: normalizedKnowledgeBaseId ?? undefined, - }), - [normalizedKnowledgeBaseId] - ) - - const handleDocumentChange = useCallback( - (documentId: string) => { - if (isPreview) return - onDocumentSelect?.(documentId) - }, - [isPreview, onDocumentSelect] - ) - - const missingKnowledgeBase = !normalizedKnowledgeBaseId - const isDisabled = finalDisabled || missingKnowledgeBase - const placeholder = subBlock.placeholder || 'Select document' - - return ( - - -
- -
-
- {missingKnowledgeBase && ( - -

Select a knowledge base first.

-
- )} -
- ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx index 6b999aacf9..405599aa9a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx @@ -13,20 +13,15 @@ import { } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' import { FIELD_TYPE_LABELS, getPlaceholderForFieldType } from '@/lib/knowledge/constants' -import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text' import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown' +import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input' import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes' -import { getBlock } from '@/blocks/registry' import type { SubBlockConfig } from '@/blocks/types' import { useKnowledgeBaseTagDefinitions } from '@/hooks/kb/use-knowledge-base-tag-definitions' import { useTagSelection } from '@/hooks/kb/use-tag-selection' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' interface DocumentTag { id: string @@ -65,26 +60,11 @@ export function DocumentTagEntry({ previewValue, previewContextValues, }: DocumentTagEntryProps) { - const { activeWorkflowId } = useWorkflowRegistry() const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id) const accessiblePrefixes = useAccessibleReferencePrefixes(blockId) const valueInputRefs = useRef>({}) const overlayRefs = useRef>({}) - const blockState = useWorkflowStore((state) => state.blocks[blockId]) - const blockConfig = blockState?.type ? getBlock(blockState.type) : null - const canonicalIndex = useMemo( - () => buildCanonicalIndex(blockConfig?.subBlocks || []), - [blockConfig?.subBlocks] - ) - const canonicalModeOverrides = blockState?.data?.canonicalModes - - const blockValues = useSubBlockStore((state) => { - if (!activeWorkflowId) return {} - const workflowValues = state.workflowValues[activeWorkflowId] || {} - return (workflowValues as Record>)[blockId] || {} - }) - const inputController = useSubBlockInput({ blockId, subBlockId: subBlock.id, @@ -97,18 +77,12 @@ export function DocumentTagEntry({ disabled, }) - const knowledgeBaseIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.knowledgeBaseId) - : resolveDependencyValue( - 'knowledgeBaseId', - blockValues, - canonicalIndex, - canonicalModeOverrides - ), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) + const { dependencyValues } = useDependsOnGate(blockId, subBlock, { + disabled, + isPreview, + previewContextValues, + }) + const knowledgeBaseIdValue = dependencyValues.knowledgeBaseSelector const knowledgeBaseId = typeof knowledgeBaseIdValue === 'string' && knowledgeBaseIdValue.trim().length > 0 ? knowledgeBaseIdValue diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx deleted file mode 100644 index 506eacc0dc..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx +++ /dev/null @@ -1,209 +0,0 @@ -'use client' - -import { useMemo } from 'react' -import { useParams } from 'next/navigation' -import { Tooltip } from '@/components/emcn' -import { getProviderIdFromServiceId } from '@/lib/oauth' -import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' -import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox' -import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' -import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' -import { getBlock } from '@/blocks/registry' -import type { SubBlockConfig } from '@/blocks/types' -import { isDependency } from '@/blocks/utils' -import { resolveSelectorForSubBlock, type SelectorResolution } from '@/hooks/selectors/resolution' -import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' - -interface FileSelectorInputProps { - blockId: string - subBlock: SubBlockConfig - disabled: boolean - isPreview?: boolean - previewValue?: any | null - previewContextValues?: Record -} - -export function FileSelectorInput({ - blockId, - subBlock, - disabled, - isPreview = false, - previewValue, - previewContextValues, -}: FileSelectorInputProps) { - const { collaborativeSetSubblockValue } = useCollaborativeWorkflow() - const { activeWorkflowId } = useWorkflowRegistry() - const params = useParams() - const workflowIdFromUrl = (params?.workflowId as string) || activeWorkflowId || '' - - const { finalDisabled } = useDependsOnGate(blockId, subBlock, { - disabled, - isPreview, - previewContextValues, - }) - - const blockState = useWorkflowStore((state) => state.blocks[blockId]) - const blockConfig = blockState?.type ? getBlock(blockState.type) : null - const canonicalIndex = useMemo( - () => buildCanonicalIndex(blockConfig?.subBlocks || []), - [blockConfig?.subBlocks] - ) - const canonicalModeOverrides = blockState?.data?.canonicalModes - - const blockValues = useSubBlockStore((state) => { - if (!activeWorkflowId) return {} - const workflowValues = state.workflowValues[activeWorkflowId] || {} - return (workflowValues as Record>)[blockId] || {} - }) - - const [domainValueFromStore] = useSubBlockValue(blockId, 'domain') - - const connectedCredential = previewContextValues - ? resolvePreviewContextValue(previewContextValues.credential) - : blockValues.credential - const domainValue = previewContextValues - ? resolvePreviewContextValue(previewContextValues.domain) - : domainValueFromStore - - const teamIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.teamId) - : resolveDependencyValue('teamId', blockValues, canonicalIndex, canonicalModeOverrides), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const siteIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.siteId) - : resolveDependencyValue('siteId', blockValues, canonicalIndex, canonicalModeOverrides), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const collectionIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.collectionId) - : resolveDependencyValue( - 'collectionId', - blockValues, - canonicalIndex, - canonicalModeOverrides - ), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const projectIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.projectId) - : resolveDependencyValue('projectId', blockValues, canonicalIndex, canonicalModeOverrides), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const planIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.planId) - : resolveDependencyValue('planId', blockValues, canonicalIndex, canonicalModeOverrides), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const normalizedCredentialId = - typeof connectedCredential === 'string' - ? connectedCredential - : typeof connectedCredential === 'object' && connectedCredential !== null - ? ((connectedCredential as Record).id ?? '') - : '' - - const serviceId = subBlock.serviceId || '' - const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId]) - - const selectorResolution = useMemo(() => { - return resolveSelectorForSubBlock(subBlock, { - workflowId: workflowIdFromUrl, - credentialId: normalizedCredentialId, - domain: (domainValue as string) || undefined, - projectId: (projectIdValue as string) || undefined, - planId: (planIdValue as string) || undefined, - teamId: (teamIdValue as string) || undefined, - siteId: (siteIdValue as string) || undefined, - collectionId: (collectionIdValue as string) || undefined, - }) - }, [ - subBlock, - workflowIdFromUrl, - normalizedCredentialId, - domainValue, - projectIdValue, - planIdValue, - teamIdValue, - siteIdValue, - collectionIdValue, - ]) - - const missingCredential = !normalizedCredentialId - const missingDomain = - selectorResolution?.key && - (selectorResolution.key === 'confluence.pages' || selectorResolution.key === 'jira.issues') && - !selectorResolution.context.domain - const missingProject = - selectorResolution?.key === 'jira.issues' && - isDependency(subBlock.dependsOn, 'projectId') && - !selectorResolution.context.projectId - const missingPlan = - selectorResolution?.key === 'microsoft.planner' && !selectorResolution.context.planId - const missingSite = - selectorResolution?.key === 'webflow.collections' && !selectorResolution.context.siteId - const missingCollection = - selectorResolution?.key === 'webflow.items' && !selectorResolution.context.collectionId - - const disabledReason = - finalDisabled || - missingCredential || - missingDomain || - missingProject || - missingPlan || - missingSite || - missingCollection || - !selectorResolution?.key - - if (!selectorResolution?.key) { - return ( - - -
- File selector not supported for service: {serviceId || 'unknown'} -
-
- -

This file selector is not implemented for {serviceId || 'unknown'}

-
-
- ) - } - - return ( - { - if (!isPreview) { - collaborativeSetSubblockValue(blockId, subBlock.id, value) - } - }} - /> - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/folder-selector/components/folder-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/folder-selector/components/folder-selector-input.tsx deleted file mode 100644 index 25fec739bc..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/folder-selector/components/folder-selector-input.tsx +++ /dev/null @@ -1,124 +0,0 @@ -'use client' - -import { useCallback, useEffect, useMemo, useState } from 'react' -import { getProviderIdFromServiceId } from '@/lib/oauth' -import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox' -import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' -import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' -import type { SubBlockConfig } from '@/blocks/types' -import { resolveSelectorForSubBlock } from '@/hooks/selectors/resolution' -import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' - -interface FolderSelectorInputProps { - blockId: string - subBlock: SubBlockConfig - disabled?: boolean - isPreview?: boolean - previewValue?: any | null - previewContextValues?: Record -} - -export function FolderSelectorInput({ - blockId, - subBlock, - disabled = false, - isPreview = false, - previewValue, - previewContextValues, -}: FolderSelectorInputProps) { - const [storeValue] = useSubBlockValue(blockId, subBlock.id) - const [credentialFromStore] = useSubBlockValue(blockId, 'credential') - const connectedCredential = previewContextValues - ? resolvePreviewContextValue(previewContextValues.credential) - : credentialFromStore - const { collaborativeSetSubblockValue } = useCollaborativeWorkflow() - const { activeWorkflowId } = useWorkflowRegistry() - const [selectedFolderId, setSelectedFolderId] = useState('') - - // Derive provider from serviceId using OAuth config (same pattern as credential-selector) - const serviceId = subBlock.serviceId || '' - const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId]) - const providerKey = serviceId.toLowerCase() - - const isCopyDestinationSelector = - subBlock.canonicalParamId === 'copyDestinationId' || - subBlock.id === 'copyDestinationFolder' || - subBlock.id === 'manualCopyDestinationFolder' - - // Central dependsOn gating - const { finalDisabled } = useDependsOnGate(blockId, subBlock, { - disabled, - isPreview, - previewContextValues, - }) - - // Get the current value from the store or prop value if in preview mode - useEffect(() => { - if (finalDisabled) return - if (isPreview && previewValue !== undefined) { - setSelectedFolderId(previewValue) - return - } - const current = storeValue as string | undefined - if (current) { - setSelectedFolderId(current) - return - } - const shouldDefaultInbox = providerKey === 'gmail' && !isCopyDestinationSelector - if (shouldDefaultInbox) { - setSelectedFolderId('INBOX') - if (!isPreview) { - collaborativeSetSubblockValue(blockId, subBlock.id, 'INBOX') - } - } - }, [ - blockId, - subBlock.id, - storeValue, - collaborativeSetSubblockValue, - isPreview, - previewValue, - finalDisabled, - providerKey, - isCopyDestinationSelector, - ]) - - const credentialId = (connectedCredential as string) || '' - const missingCredential = credentialId.length === 0 - const selectorResolution = useMemo( - () => - resolveSelectorForSubBlock(subBlock, { - credentialId: credentialId || undefined, - workflowId: activeWorkflowId || undefined, - }), - [subBlock, credentialId, activeWorkflowId] - ) - - const handleChange = useCallback( - (value: string) => { - setSelectedFolderId(value) - if (!isPreview) { - collaborativeSetSubblockValue(blockId, subBlock.id, value) - } - }, - [blockId, subBlock.id, collaborativeSetSubblockValue, isPreview] - ) - - return ( - - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts index 33ccd2acd4..af6314a94f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts @@ -3,14 +3,11 @@ export { Code } from './code/code' export { ComboBox } from './combobox/combobox' export { ConditionInput } from './condition-input/condition-input' export { CredentialSelector } from './credential-selector/credential-selector' -export { DocumentSelector } from './document-selector/document-selector' export { DocumentTagEntry } from './document-tag-entry/document-tag-entry' export { Dropdown } from './dropdown/dropdown' export { EvalInput } from './eval-input/eval-input' -export { FileSelectorInput } from './file-selector/file-selector-input' export { FileUpload } from './file-upload/file-upload' export { FilterBuilder } from './filter-builder/filter-builder' -export { FolderSelectorInput } from './folder-selector/components/folder-selector-input' export { GroupedCheckboxList } from './grouped-checkbox-list/grouped-checkbox-list' export { InputMapping } from './input-mapping/input-mapping' export { KnowledgeBaseSelector } from './knowledge-base-selector/knowledge-base-selector' @@ -20,13 +17,11 @@ export { McpDynamicArgs } from './mcp-dynamic-args/mcp-dynamic-args' export { McpServerSelector } from './mcp-server-modal/mcp-server-selector' export { McpToolSelector } from './mcp-server-modal/mcp-tool-selector' export { MessagesInput } from './messages-input/messages-input' -export { ProjectSelectorInput } from './project-selector/project-selector-input' export { ResponseFormat } from './response/response-format' export { ScheduleInfo } from './schedule-info/schedule-info' -export { SheetSelectorInput } from './sheet-selector/sheet-selector-input' +export { SelectorInput, type SelectorOverrides } from './selector-input/selector-input' export { ShortInput } from './short-input/short-input' export { SkillInput } from './skill-input/skill-input' -export { SlackSelectorInput } from './slack-selector/slack-selector-input' export { SliderInput } from './slider-input/slider-input' export { SortBuilder } from './sort-builder/sort-builder' export { InputFormat } from './starter/input-format' diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx index 92d1532d1c..2d36e69de5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useRef } from 'react' +import { useRef } from 'react' import { Plus } from 'lucide-react' import { Badge, @@ -14,19 +14,14 @@ import { import { cn } from '@/lib/core/utils/cn' import { FIELD_TYPE_LABELS, getPlaceholderForFieldType } from '@/lib/knowledge/constants' import { type FilterFieldType, getOperatorsForFieldType } from '@/lib/knowledge/filters/types' -import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text' import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown' +import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes' -import { getBlock } from '@/blocks/registry' import type { SubBlockConfig } from '@/blocks/types' import { useKnowledgeBaseTagDefinitions } from '@/hooks/kb/use-knowledge-base-tag-definitions' import { useTagSelection } from '@/hooks/kb/use-tag-selection' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { useSubBlockValue } from '../../hooks/use-sub-block-value' interface TagFilter { @@ -69,38 +64,17 @@ export function KnowledgeTagFilters({ previewValue, previewContextValues, }: KnowledgeTagFiltersProps) { - const { activeWorkflowId } = useWorkflowRegistry() const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id) const emitTagSelection = useTagSelection(blockId, subBlock.id) const valueInputRefs = useRef>({}) const overlayRefs = useRef>({}) - const blockState = useWorkflowStore((state) => state.blocks[blockId]) - const blockConfig = blockState?.type ? getBlock(blockState.type) : null - const canonicalIndex = useMemo( - () => buildCanonicalIndex(blockConfig?.subBlocks || []), - [blockConfig?.subBlocks] - ) - const canonicalModeOverrides = blockState?.data?.canonicalModes - - const blockValues = useSubBlockStore((state) => { - if (!activeWorkflowId) return {} - const workflowValues = state.workflowValues[activeWorkflowId] || {} - return (workflowValues as Record>)[blockId] || {} + const { dependencyValues } = useDependsOnGate(blockId, subBlock, { + disabled, + isPreview, + previewContextValues, }) - - const knowledgeBaseIdValue = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.knowledgeBaseId) - : resolveDependencyValue( - 'knowledgeBaseId', - blockValues, - canonicalIndex, - canonicalModeOverrides - ), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) + const knowledgeBaseIdValue = dependencyValues.knowledgeBaseSelector const knowledgeBaseId = typeof knowledgeBaseIdValue === 'string' && knowledgeBaseIdValue.trim().length > 0 ? knowledgeBaseIdValue diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/project-selector/project-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/project-selector/project-selector-input.tsx deleted file mode 100644 index 3df3acd464..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/project-selector/project-selector-input.tsx +++ /dev/null @@ -1,140 +0,0 @@ -'use client' - -import { useEffect, useMemo, useState } from 'react' -import { useParams } from 'next/navigation' -import { Tooltip } from '@/components/emcn' -import { getProviderIdFromServiceId } from '@/lib/oauth' -import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' -import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox' -import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' -import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' -import { getBlock } from '@/blocks/registry' -import type { SubBlockConfig } from '@/blocks/types' -import { resolveSelectorForSubBlock } from '@/hooks/selectors/resolution' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' - -interface ProjectSelectorInputProps { - blockId: string - subBlock: SubBlockConfig - disabled?: boolean - onProjectSelect?: (projectId: string) => void - isPreview?: boolean - previewValue?: any | null - previewContextValues?: Record -} - -export function ProjectSelectorInput({ - blockId, - subBlock, - disabled = false, - onProjectSelect, - isPreview = false, - previewValue, - previewContextValues, -}: ProjectSelectorInputProps) { - const params = useParams() - const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId) as string | null - const [selectedProjectId, setSelectedProjectId] = useState('') - const [storeValue] = useSubBlockValue(blockId, subBlock.id) - const [jiraDomainFromStore] = useSubBlockValue(blockId, 'domain') - - const blockState = useWorkflowStore((state) => state.blocks[blockId]) - const blockConfig = blockState?.type ? getBlock(blockState.type) : null - const canonicalIndex = useMemo( - () => buildCanonicalIndex(blockConfig?.subBlocks || []), - [blockConfig?.subBlocks] - ) - const canonicalModeOverrides = blockState?.data?.canonicalModes - - const blockValues = useSubBlockStore((state) => { - if (!activeWorkflowId) return {} - const workflowValues = state.workflowValues[activeWorkflowId] || {} - return (workflowValues as Record>)[blockId] || {} - }) - - const connectedCredential = previewContextValues - ? resolvePreviewContextValue(previewContextValues.credential) - : blockValues.credential - const jiraDomain = previewContextValues - ? resolvePreviewContextValue(previewContextValues.domain) - : jiraDomainFromStore - - const linearTeamId = useMemo( - () => - previewContextValues - ? resolvePreviewContextValue(previewContextValues.teamId) - : resolveDependencyValue('teamId', blockValues, canonicalIndex, canonicalModeOverrides), - [previewContextValues, blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const serviceId = subBlock.serviceId || '' - const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId]) - const workflowIdFromUrl = (params?.workflowId as string) || activeWorkflowId || '' - const { finalDisabled } = useDependsOnGate(blockId, subBlock, { - disabled, - isPreview, - previewContextValues, - }) - - const domain = (jiraDomain as string) || '' - - useEffect(() => { - if (isPreview && previewValue !== undefined) { - setSelectedProjectId(previewValue) - } else if (typeof storeValue === 'string') { - setSelectedProjectId(storeValue) - } else { - setSelectedProjectId('') - } - }, [isPreview, previewValue, storeValue]) - - const selectorResolution = useMemo(() => { - return resolveSelectorForSubBlock(subBlock, { - workflowId: workflowIdFromUrl || undefined, - credentialId: (connectedCredential as string) || undefined, - domain, - teamId: (linearTeamId as string) || undefined, - }) - }, [subBlock, workflowIdFromUrl, connectedCredential, domain, linearTeamId]) - - const missingCredential = !selectorResolution?.context.credentialId - - const handleChange = (value: string) => { - setSelectedProjectId(value) - onProjectSelect?.(value) - } - - return ( - - -
- {selectorResolution?.key ? ( - - ) : ( -
- Project selector not supported for service: {serviceId} -
- )} -
-
- {missingCredential && ( - -

Please select an account first

-
- )} -
- ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-input/selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-input/selector-input.tsx new file mode 100644 index 0000000000..de6c1df71d --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-input/selector-input.tsx @@ -0,0 +1,106 @@ +'use client' + +import { useEffect, useRef } from 'react' +import { Tooltip } from '@/components/emcn' +import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox' +import { useSelectorSetup } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-selector-setup' +import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' +import type { SubBlockConfig } from '@/blocks/types' +import type { SelectorContext } from '@/hooks/selectors/types' +import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' + +export interface SelectorOverrides { + transformContext?: (context: SelectorContext, deps: Record) => SelectorContext + getDefaultValue?: (subBlock: SubBlockConfig) => string | null +} + +interface SelectorInputProps { + blockId: string + subBlock: SubBlockConfig + disabled?: boolean + isPreview?: boolean + previewValue?: any + previewContextValues?: Record + overrides?: SelectorOverrides +} + +export function SelectorInput({ + blockId, + subBlock, + disabled = false, + isPreview = false, + previewValue, + previewContextValues, + overrides, +}: SelectorInputProps) { + const { collaborativeSetSubblockValue } = useCollaborativeWorkflow() + const [storeValue] = useSubBlockValue(blockId, subBlock.id) + const defaultAppliedRef = useRef(false) + + const { + selectorKey, + selectorContext: autoContext, + allowSearch, + disabled: selectorDisabled, + dependencyValues, + } = useSelectorSetup(blockId, subBlock, { disabled, isPreview, previewContextValues }) + + const selectorContext = overrides?.transformContext + ? overrides.transformContext(autoContext, dependencyValues) + : autoContext + + useEffect(() => { + if (defaultAppliedRef.current || isPreview || selectorDisabled) return + if (storeValue) return + + const defaultValue = overrides?.getDefaultValue?.(subBlock) + if (defaultValue) { + defaultAppliedRef.current = true + collaborativeSetSubblockValue(blockId, subBlock.id, defaultValue) + } + }, [ + blockId, + subBlock, + storeValue, + isPreview, + selectorDisabled, + overrides, + collaborativeSetSubblockValue, + ]) + + const serviceId = subBlock.serviceId || 'unknown' + + if (!selectorKey) { + return ( + + +
+ Selector not supported for service: {serviceId} +
+
+ +

This selector is not implemented for {serviceId}

+
+
+ ) + } + + return ( + { + if (!isPreview) { + collaborativeSetSubblockValue(blockId, subBlock.id, value) + } + }} + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sheet-selector/sheet-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sheet-selector/sheet-selector-input.tsx deleted file mode 100644 index ee33b320a6..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sheet-selector/sheet-selector-input.tsx +++ /dev/null @@ -1,136 +0,0 @@ -'use client' - -import { useMemo } from 'react' -import { useParams } from 'next/navigation' -import { Tooltip } from '@/components/emcn' -import { getProviderIdFromServiceId } from '@/lib/oauth' -import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' -import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox' -import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' -import { getBlock } from '@/blocks/registry' -import type { SubBlockConfig } from '@/blocks/types' -import { resolveSelectorForSubBlock, type SelectorResolution } from '@/hooks/selectors/resolution' -import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' - -interface SheetSelectorInputProps { - blockId: string - subBlock: SubBlockConfig - disabled: boolean - isPreview?: boolean - previewValue?: any | null - previewContextValues?: Record -} - -export function SheetSelectorInput({ - blockId, - subBlock, - disabled, - isPreview = false, - previewValue, - previewContextValues, -}: SheetSelectorInputProps) { - const { collaborativeSetSubblockValue } = useCollaborativeWorkflow() - const { activeWorkflowId } = useWorkflowRegistry() - const params = useParams() - const workflowIdFromUrl = (params?.workflowId as string) || activeWorkflowId || '' - - const { finalDisabled } = useDependsOnGate(blockId, subBlock, { - disabled, - isPreview, - previewContextValues, - }) - - const blockState = useWorkflowStore((state) => state.blocks[blockId]) - const blockConfig = blockState?.type ? getBlock(blockState.type) : null - const canonicalIndex = useMemo( - () => buildCanonicalIndex(blockConfig?.subBlocks || []), - [blockConfig?.subBlocks] - ) - const canonicalModeOverrides = blockState?.data?.canonicalModes - - const blockValues = useSubBlockStore((state) => { - if (!activeWorkflowId) return {} - const workflowValues = state.workflowValues[activeWorkflowId] || {} - return (workflowValues as Record>)[blockId] || {} - }) - - const connectedCredentialFromStore = blockValues.credential - - const spreadsheetIdFromStore = useMemo( - () => - resolveDependencyValue('spreadsheetId', blockValues, canonicalIndex, canonicalModeOverrides), - [blockValues, canonicalIndex, canonicalModeOverrides] - ) - - const connectedCredential = previewContextValues - ? resolvePreviewContextValue(previewContextValues.credential) - : connectedCredentialFromStore - const spreadsheetId = previewContextValues - ? (resolvePreviewContextValue(previewContextValues.spreadsheetId) ?? - resolvePreviewContextValue(previewContextValues.manualSpreadsheetId)) - : spreadsheetIdFromStore - - const normalizedCredentialId = - typeof connectedCredential === 'string' - ? connectedCredential - : typeof connectedCredential === 'object' && connectedCredential !== null - ? ((connectedCredential as Record).id ?? '') - : '' - - const normalizedSpreadsheetId = typeof spreadsheetId === 'string' ? spreadsheetId.trim() : '' - - const serviceId = subBlock.serviceId || '' - const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId]) - - const selectorResolution = useMemo(() => { - return resolveSelectorForSubBlock(subBlock, { - workflowId: workflowIdFromUrl, - credentialId: normalizedCredentialId, - spreadsheetId: normalizedSpreadsheetId, - }) - }, [subBlock, workflowIdFromUrl, normalizedCredentialId, normalizedSpreadsheetId]) - - const missingCredential = !normalizedCredentialId - const missingSpreadsheet = !normalizedSpreadsheetId - - const disabledReason = - finalDisabled || missingCredential || missingSpreadsheet || !selectorResolution?.key - - if (!selectorResolution?.key) { - return ( - - -
- Sheet selector not supported for service: {serviceId || 'unknown'} -
-
- -

This sheet selector is not implemented for {serviceId || 'unknown'}

-
-
- ) - } - - return ( - { - if (!isPreview) { - collaborativeSetSubblockValue(blockId, subBlock.id, value) - } - }} - /> - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-selector/slack-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-selector/slack-selector-input.tsx deleted file mode 100644 index e3e4e21485..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-selector/slack-selector-input.tsx +++ /dev/null @@ -1,148 +0,0 @@ -'use client' - -import { useEffect, useMemo, useState } from 'react' -import { useParams } from 'next/navigation' -import { Tooltip } from '@/components/emcn' -import { getProviderIdFromServiceId } from '@/lib/oauth' -import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox' -import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' -import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' -import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils' -import type { SubBlockConfig } from '@/blocks/types' -import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types' - -type SlackSelectorType = 'channel-selector' | 'user-selector' - -const SELECTOR_CONFIG: Record< - SlackSelectorType, - { selectorKey: SelectorKey; placeholder: string; label: string } -> = { - 'channel-selector': { - selectorKey: 'slack.channels', - placeholder: 'Select Slack channel', - label: 'Channel', - }, - 'user-selector': { - selectorKey: 'slack.users', - placeholder: 'Select Slack user', - label: 'User', - }, -} - -interface SlackSelectorInputProps { - blockId: string - subBlock: SubBlockConfig - disabled?: boolean - onSelect?: (value: string) => void - isPreview?: boolean - previewValue?: any | null - previewContextValues?: Record -} - -export function SlackSelectorInput({ - blockId, - subBlock, - disabled = false, - onSelect, - isPreview = false, - previewValue, - previewContextValues, -}: SlackSelectorInputProps) { - const selectorType = subBlock.type as SlackSelectorType - const config = SELECTOR_CONFIG[selectorType] - - const params = useParams() - const workflowIdFromUrl = (params?.workflowId as string) || '' - const [storeValue] = useSubBlockValue(blockId, subBlock.id) - const [authMethod] = useSubBlockValue(blockId, 'authMethod') - const [botToken] = useSubBlockValue(blockId, 'botToken') - const [connectedCredential] = useSubBlockValue(blockId, 'credential') - - const effectiveAuthMethod = previewContextValues - ? resolvePreviewContextValue(previewContextValues.authMethod) - : authMethod - const effectiveBotToken = previewContextValues - ? resolvePreviewContextValue(previewContextValues.botToken) - : botToken - const effectiveCredential = previewContextValues - ? resolvePreviewContextValue(previewContextValues.credential) - : connectedCredential - const [_selectedValue, setSelectedValue] = useState(null) - - const serviceId = subBlock.serviceId || '' - const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId]) - const isSlack = serviceId === 'slack' - - const { finalDisabled, dependsOn } = useDependsOnGate(blockId, subBlock, { - disabled, - isPreview, - previewContextValues, - }) - - const credential: string = - (effectiveAuthMethod as string) === 'bot_token' - ? (effectiveBotToken as string) || '' - : (effectiveCredential as string) || '' - - useEffect(() => { - const val = isPreview && previewValue !== undefined ? previewValue : storeValue - if (typeof val === 'string') { - setSelectedValue(val) - } - }, [isPreview, previewValue, storeValue]) - - const requiresCredential = dependsOn.includes('credential') - const missingCredential = !credential || credential.trim().length === 0 - const shouldForceDisable = requiresCredential && missingCredential - - const context: SelectorContext = useMemo( - () => ({ - credentialId: credential, - workflowId: workflowIdFromUrl, - }), - [credential, workflowIdFromUrl] - ) - - if (!isSlack) { - return ( - - -
- {config.label} selector not supported for service: {serviceId || 'unknown'} -
-
- -

- This {config.label.toLowerCase()} selector is not yet implemented for{' '} - {serviceId || 'unknown'} -

-
-
- ) - } - - return ( - - -
- { - setSelectedValue(value) - if (!isPreview) { - onSelect?.(value) - } - }} - /> -
-
-
- ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts index 9673f11c7d..0fd65d8f3d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts @@ -177,5 +177,7 @@ export function useDependsOnGate( depsSatisfied, blocked, finalDisabled, + dependencyValues: dependencyValuesMap, + canonicalIndex, } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-selector-setup.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-selector-setup.ts new file mode 100644 index 0000000000..ff6d5e43b6 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-selector-setup.ts @@ -0,0 +1,81 @@ +'use client' + +import { useMemo } from 'react' +import { useParams } from 'next/navigation' +import type { SubBlockConfig } from '@/blocks/types' +import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types' +import { useWorkflowRegistry } from '@/stores/workflows/registry/store' +import { useDependsOnGate } from './use-depends-on-gate' + +/** + * Resolves all selector configuration from a sub-block's declarative properties. + * + * Builds a `SelectorContext` by mapping each `dependsOn` entry through the + * canonical index to its `canonicalParamId`, which maps directly to + * `SelectorContext` field names (e.g. `siteId`, `teamId`, `collectionId`). + * The one special case is `oauthCredential` which maps to `credentialId`. + * + * @param blockId - The block containing the selector sub-block + * @param subBlock - The sub-block config (must have `selectorKey` set) + * @param opts - Standard disabled/preview/previewContextValues options + * @returns Everything `SelectorCombobox` needs: key, context, disabled, allowSearch, plus raw dependency values + */ +export function useSelectorSetup( + blockId: string, + subBlock: SubBlockConfig, + opts?: { disabled?: boolean; isPreview?: boolean; previewContextValues?: Record } +) { + const params = useParams() + const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId) + const workflowId = (params?.workflowId as string) || activeWorkflowId || '' + + const { finalDisabled, dependencyValues, canonicalIndex } = useDependsOnGate( + blockId, + subBlock, + opts + ) + + const selectorContext = useMemo(() => { + const context: SelectorContext = { + workflowId, + mimeType: subBlock.mimeType, + } + + for (const [depKey, value] of Object.entries(dependencyValues)) { + if (value === null || value === undefined) continue + const strValue = String(value) + if (!strValue) continue + + const canonicalParamId = canonicalIndex.canonicalIdBySubBlockId[depKey] ?? depKey + + if (canonicalParamId === 'oauthCredential') { + context.credentialId = strValue + } else if (canonicalParamId in CONTEXT_FIELD_SET) { + ;(context as Record)[canonicalParamId] = strValue + } + } + + return context + }, [dependencyValues, canonicalIndex, workflowId, subBlock.mimeType]) + + return { + selectorKey: (subBlock.selectorKey ?? null) as SelectorKey | null, + selectorContext, + allowSearch: subBlock.selectorAllowSearch ?? true, + disabled: finalDisabled || !subBlock.selectorKey, + dependencyValues, + } +} + +const CONTEXT_FIELD_SET: Record = { + credentialId: true, + domain: true, + teamId: true, + projectId: true, + knowledgeBaseId: true, + planId: true, + siteId: true, + collectionId: true, + spreadsheetId: true, + fileId: true, +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index 07253f6d7b..3887b46e55 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -18,14 +18,11 @@ import { ComboBox, ConditionInput, CredentialSelector, - DocumentSelector, DocumentTagEntry, Dropdown, EvalInput, - FileSelectorInput, FileUpload, FilterBuilder, - FolderSelectorInput, GroupedCheckboxList, InputFormat, InputMapping, @@ -36,13 +33,12 @@ import { McpServerSelector, McpToolSelector, MessagesInput, - ProjectSelectorInput, ResponseFormat, ScheduleInfo, - SheetSelectorInput, + SelectorInput, + type SelectorOverrides, ShortInput, SkillInput, - SlackSelectorInput, SliderInput, SortBuilder, Switch, @@ -58,6 +54,23 @@ import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/c import type { SubBlockConfig } from '@/blocks/types' import { useWebhookManagement } from '@/hooks/use-webhook-management' +const SLACK_OVERRIDES: SelectorOverrides = { + transformContext: (context, deps) => { + const authMethod = deps.authMethod as string + const credentialId = + authMethod === 'bot_token' ? String(deps.botToken ?? '') : String(deps.credential ?? '') + return { ...context, credentialId } + }, +} + +const FOLDER_OVERRIDES: SelectorOverrides = { + getDefaultValue: (subBlock) => { + const isGmail = subBlock.serviceId === 'gmail' + const isCopyDest = subBlock.canonicalParamId === 'copyDestinationId' + return isGmail && !isCopyDest ? 'INBOX' : null + }, +} + /** * Interface for wand control handlers exposed by sub-block inputs */ @@ -901,32 +914,10 @@ function SubBlockComponent({ ) case 'file-selector': - return ( - - ) - case 'sheet-selector': - return ( - - ) - case 'project-selector': return ( - ) @@ -985,12 +977,12 @@ function SubBlockComponent({ case 'document-selector': return ( - ) @@ -1068,13 +1060,14 @@ function SubBlockComponent({ case 'channel-selector': case 'user-selector': return ( - ) diff --git a/apps/sim/blocks/blocks/knowledge.ts b/apps/sim/blocks/blocks/knowledge.ts index bb5e20f3f9..b83bfe7639 100644 --- a/apps/sim/blocks/blocks/knowledge.ts +++ b/apps/sim/blocks/blocks/knowledge.ts @@ -69,6 +69,7 @@ export const KnowledgeBlock: BlockConfig = { title: 'Tag Filters', type: 'knowledge-tag-filters', placeholder: 'Add tag filters', + dependsOn: ['knowledgeBaseSelector'], condition: { field: 'operation', value: 'search' }, }, { @@ -112,6 +113,7 @@ export const KnowledgeBlock: BlockConfig = { id: 'documentTags', title: 'Document Tags', type: 'document-tag-entry', + dependsOn: ['knowledgeBaseSelector'], condition: { field: 'operation', value: 'create_document' }, }, ],