Skip to main content
Glama
auto-form-field-wrapper.tsx10.2 kB
import { t } from 'i18next'; import { Calendar, SquareFunction, File } from 'lucide-react'; import React from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { ControllerRenderProps, useFormContext } from 'react-hook-form'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { FormItem, FormLabel } from '@/components/ui/form'; import { ReadMoreDescription } from '@/components/ui/read-more-description'; import { Toggle } from '@/components/ui/toggle'; import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; import { formUtils } from '@/features/pieces/lib/form-utils'; import { cn } from '@/lib/utils'; import { PieceAuthProperty, PieceProperty, PropertyType, } from '@activepieces/pieces-framework'; import { FlowAction, FlowTrigger, PropertyExecutionType, } from '@activepieces/shared'; import { ArrayPiecePropertyInInlineItemMode } from './array-property-in-inline-item-mode'; import { TextInputWithMentions } from './text-input-with-mentions'; function AutoFormFieldWrapper({ placeBeforeLabelText = false, children, allowDynamicValues, propertyName, inputName, property, disabled, field, dynamicInputModeToggled, //we have to pass this prop, because props inside custom auth can be secret text, which means their labels will become (Connection) isForConnectionSelect = false, }: AutoFormFieldWrapperProps) { const isArrayProperty = !isPieceAuthProperty(property) && property.type === PropertyType.ARRAY; const isAuthProperty = isForConnectionSelect || Array.isArray(property); return ( <AutoFormFielWrapperErrorBoundary field={field} property={property ?? null} dynamicInputModeToggled={dynamicInputModeToggled} > <FormItem className="flex flex-col gap-1"> <FormLabel className="flex items-center gap-1 "> {placeBeforeLabelText && !dynamicInputModeToggled && children} <div className="pt-1"> <span> {isAuthProperty ? t('Connection') : property.displayName} </span>{' '} {(isAuthProperty || property.required) && ( <span className="text-destructive">*</span> )} </div> {property && !isAuthProperty && ( <PropertyTypeTooltip property={property} /> )} <span className="grow"></span> {allowDynamicValues && ( <DynamicValueToggle propertyName={propertyName} inputName={inputName} property={property} disabled={disabled} isToggled={dynamicInputModeToggled ?? false} /> )} </FormLabel> {dynamicInputModeToggled && !isArrayProperty && ( <TextInputWithMentions disabled={disabled} onChange={field.onChange} initialValue={field.value ?? null} /> )} {isArrayProperty && dynamicInputModeToggled && ( <ArrayPiecePropertyInInlineItemMode disabled={disabled} arrayProperties={property.properties} inputName={inputName} onChange={field.onChange} value={field.value ?? null} /> )} {!placeBeforeLabelText && !dynamicInputModeToggled && ( <div>{children}</div> )} {!isForConnectionSelect && !Array.isArray(property) && property.description && ( <ReadMoreDescription text={t(property.description)} /> )} </FormItem> </AutoFormFielWrapperErrorBoundary> ); } function AutoFormFielWrapperErrorBoundary({ children, field, property, dynamicInputModeToggled, }: AutoFormFielWrapperErrorBoundaryProps) { return ( <ErrorBoundary fallbackRender={() => ( <div className="text-sm flex items-center justify-between"> <div className="text-red-500"> {t('input value is invalid, please contact support')} </div> <Button variant="outline" size="sm" onClick={() => { navigator.clipboard.writeText( JSON.stringify({ stringifiedValue: stringifyValue(field.value), property, dynamicInputModeToggled, disabled: field.disabled, }), ); toast(t('Info copied to clipboard, please send it to support'), { duration: 3000, }); }} > {t('Info')} </Button> </div> )} > {children} </ErrorBoundary> ); } function getValueForInputOnDynamicToggleChange( property: PieceProperty | PieceAuthProperty[], newMode: PropertyExecutionType, currentValue: unknown, ) { const isAuthProperty = isPieceAuthProperty(property); switch (newMode) { case PropertyExecutionType.DYNAMIC: { if (!isAuthProperty && property.type === PropertyType.ARRAY) { return formUtils.getDefaultPropertyValue({ property, dynamicInputModeToggled: true, }); } //to show what the selected value is for dropdowns if ( typeof currentValue === 'string' || typeof currentValue === 'number' ) { return currentValue; } return JSON.stringify(currentValue); } case PropertyExecutionType.MANUAL: if (isAuthProperty) { return ''; } return formUtils.getDefaultPropertyValue({ property, dynamicInputModeToggled: false, }); } } function DynamicValueToggle({ propertyName, inputName, property, disabled, isToggled, }: DynamicValueToggleProps) { const form = useFormContext<FlowAction | FlowTrigger>(); function updatePropertySettings(mode: PropertyExecutionType) { const propertySettingsForSingleProperty = { ...form.getValues().settings?.propertySettings?.[propertyName], type: mode, }; form.setValue( `settings.propertySettings.${propertyName}`, propertySettingsForSingleProperty, ); } function handleDynamicValueToggleChange(mode: PropertyExecutionType) { updatePropertySettings(mode); if (isInputNameLiteral(inputName)) { const currentValue = form.getValues(inputName); const newValue = getValueForInputOnDynamicToggleChange( property, mode, currentValue, ); form.setValue(inputName, newValue, { shouldValidate: true, }); } else { throw new Error( 'inputName is not a member of step settings input, you might be using dynamic properties where you should not', ); } } return ( <div className="flex gap-2 items-center"> <Tooltip> <TooltipTrigger asChild> <Toggle pressed={isToggled} onPressedChange={(newIsToggled) => handleDynamicValueToggleChange( newIsToggled ? PropertyExecutionType.DYNAMIC : PropertyExecutionType.MANUAL, ) } disabled={disabled} > <SquareFunction className={cn('size-5', { 'text-foreground': isToggled, 'text-muted-foreground': !isToggled, })} /> </Toggle> </TooltipTrigger> <TooltipContent side="top" className="bg-background"> {t('Dynamic value')} </TooltipContent> </Tooltip> </div> ); } function PropertyTypeTooltip({ property }: { property: PieceProperty }) { if ( property.type !== PropertyType.FILE && property.type !== PropertyType.DATE_TIME ) { return null; } return ( <Tooltip> <TooltipTrigger asChild> {property.type === PropertyType.FILE ? ( <File className="w-4 h-4 stroke-foreground/55"></File> ) : ( property.type === PropertyType.DATE_TIME && ( <Calendar className="w-4 h-4 stroke-foreground/55"></Calendar> ) )} </TooltipTrigger> <TooltipContent side="bottom"> <> {property.type === PropertyType.FILE && t('File Input i.e a url or file passed from a previous step')} {property.type === PropertyType.DATE_TIME && t('Date Input must comply with ISO 8601 format')} </> </TooltipContent> </Tooltip> ); } function stringifyValue(value: unknown) { try { if (typeof value === 'string' || typeof value === 'number') { return value; } return JSON.stringify(value); } catch (e) { return value; } } AutoFormFieldWrapper.displayName = 'AutoFormFieldWrapper'; export { AutoFormFieldWrapper }; type DynamicValueToggleProps = { propertyName: string; inputName: string; property: PieceProperty | PieceAuthProperty[]; disabled: boolean; isToggled: boolean; }; type AutoFormFieldWrapperProps = { children: React.ReactNode; allowDynamicValues: boolean; propertyName: string; hideDescription?: boolean; placeBeforeLabelText?: boolean; disabled: boolean; field: ControllerRenderProps<any, string>; inputName: string; dynamicInputModeToggled?: boolean; property: PieceProperty | PieceAuthProperty[]; isForConnectionSelect?: boolean; }; type AutoFormFielWrapperErrorBoundaryProps = { children: React.ReactNode; field: ControllerRenderProps; property: PieceProperty | PieceAuthProperty[] | null; dynamicInputModeToggled?: boolean; }; function isInputNameLiteral( inputName: string, ): inputName is `settings.input.${string}` { return inputName.match(/settings\.input\./) !== null; } function isPieceAuthProperty( property: PieceProperty | PieceAuthProperty[], ): property is PieceAuthProperty[] { const authPropertyTypes = [ PropertyType.SECRET_TEXT, PropertyType.BASIC_AUTH, PropertyType.OAUTH2, PropertyType.CUSTOM_AUTH, ]; return ( Array.isArray(property) || authPropertyTypes.some((authType) => property.type === authType) ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server