import * as React from 'react'; import { SubmissionMetadata, changeGroupVisibility, submitQuestion, updateCustomQuestionName, updateParamValue, updateQuestionWeight } from 'wdk-client/Actions/QuestionActions'; import { HelpIcon, IconAlt, Link } from 'wdk-client/Components'; import { ResetFormButton, ResetFormConfig } from 'wdk-client/Components/Shared/ResetFormButton'; import { DispatchAction } from 'wdk-client/Core/CommonTypes'; import { QuestionState, QuestionWithMappedParameters } from 'wdk-client/StoreModules/QuestionStoreModule'; import { makeClassNameHelper, safeHtml } from 'wdk-client/Utils/ComponentUtils'; import { scrollIntoView } from 'wdk-client/Utils/DomUtils'; import { Seq } from 'wdk-client/Utils/IterableUtils'; import { Parameter, ParameterGroup, RecordClass } from 'wdk-client/Utils/WdkModel'; import { makeParamDependenciesUpdating, useDependentParamsAreUpdating } from 'wdk-client/Views/Question/Params/Utils'; import StepValidationInfo from 'wdk-client/Views/Question/StepValidationInfo'; import { Tabs } from 'wdk-client/Components'; import 'wdk-client/Views/Question/DefaultQuestionForm.scss'; type TextboxChangeHandler = (event: React.ChangeEvent) => void; type EventHandlers = { setGroupVisibility: typeof changeGroupVisibility, updateParamValue: typeof updateParamValue } export type Props = { state: QuestionState; dispatchAction: DispatchAction; eventHandlers: EventHandlers; parameterElements: Record; submissionMetadata: SubmissionMetadata; recordClass: RecordClass; submitButtonText?: string; validateForm?: boolean; renderParamGroup?: (group: ParameterGroup, formProps: Props) => JSX.Element; DescriptionComponent?: (props: { description?: string }) => JSX.Element; DatasetsComponent?: () => JSX.Element; onSubmit?: (e: React.FormEvent) => boolean | void; resetFormConfig: ResetFormConfig; containerClassName?: string; } const cx = makeClassNameHelper('wdk-QuestionForm'); // FIXME Should be made nicer once we upgrade to a version of Redux that supports hooks export const useDefaultOnSubmit = ( dispatchAction: DispatchAction, urlSegment: string, submissionMetadata: SubmissionMetadata, forWebServicesTutorial: boolean ) => React.useCallback( (event: React.FormEvent) => { event.preventDefault(); dispatchAction(submitQuestion({ searchName: urlSegment, submissionMetadata: { ...submissionMetadata, ...( submissionMetadata.type === 'create-strategy' ? { webServicesTutorialSubmission: forWebServicesTutorial } : {} ) } })); }, [dispatchAction, urlSegment, submissionMetadata, forWebServicesTutorial] ); export default function DefaultQuestionForm(props: Props) { const { dispatchAction, onSubmit, submissionMetadata, state, submitButtonText, recordClass, validateForm = true, containerClassName, resetFormConfig } = props; const { question, customName, paramValues, weight, stepValidation, submitting } = state; let defaultOnSubmit = useDefaultOnSubmit(dispatchAction, question.urlSegment, submissionMetadata, false); let defaultOnWebservicesLinkClick = useDefaultOnSubmit(dispatchAction, question.urlSegment, submissionMetadata, true); let dependentParamsAreUpdating = useDependentParamsAreUpdating( question, state.paramsUpdatingDependencies ); let submissionDisabled = dependentParamsAreUpdating; let handleSubmit = React.useCallback( (event: React.FormEvent) => { if ( submissionDisabled || ( onSubmit != null && !onSubmit(event) ) ) { return false; } return defaultOnSubmit(event); }, [onSubmit, defaultOnSubmit, submissionDisabled] ); let handleWebservicesTutorialLinkClick = React.useCallback( (event: React.MouseEvent) => { if (onSubmit && !onSubmit(event)) { return false; } return defaultOnWebservicesLinkClick(event); }, [onSubmit, defaultOnWebservicesLinkClick] ); let handleCustomNameChange = (event: React.ChangeEvent) => { dispatchAction(updateCustomQuestionName({ searchName: question.urlSegment, customName: event.target.value })); } let handleWeightChange = (event: React.ChangeEvent) => { dispatchAction(updateQuestionWeight({ searchName: question.urlSegment, weight: event.target.value })); } let renderParamGroup = props.renderParamGroup ? props.renderParamGroup : renderDefaultParamGroup; let Description = props.DescriptionComponent || QuestionDescription; let fullContainerClassName = `${containerClassName || ''} ` + cx(); let containerRef = React.useRef(null); React.useEffect(() => { if (stepValidation && !stepValidation.isValid && containerRef.current instanceof HTMLElement) { scrollIntoView(containerRef.current); } }, [stepValidation]); const [selectedTab, setSelectedTab] = React.useState('form'); const standardTabs = [ { key: 'form', display: 'Configure Search', content: ( <> {resetFormConfig.offered && }
{question.groups .filter(group => group.displayType !== 'hidden') .map(group => renderParamGroup(group, props)) } ), }, { key: 'description', display: 'Learn More', content: , }, ] return (
); } type QuestionHeaderProps = { headerText: string, showHeader: boolean, }; export function QuestionHeader(props: QuestionHeaderProps) { return props.showHeader ? (

{props.headerText}

) : <>; } export function renderDefaultParamGroup(group: ParameterGroup, formProps: Props) { let { state, eventHandlers, parameterElements } = formProps; let { question, groupUIState, paramsUpdatingDependencies } = state; const paramDependenciesUpdating = makeParamDependenciesUpdating( question, paramsUpdatingDependencies ); return ( ); } type DefaultGroupProps = { question: QuestionWithMappedParameters; group: ParameterGroup; uiState: any; onVisibilityChange: EventHandlers['setGroupVisibility']; parameterElements: Record; paramDependenciesUpdating: Record; } export function DefaultGroup(props: DefaultGroupProps) { let { question, group, uiState, onVisibilityChange, parameterElements, paramDependenciesUpdating } = props; return ( ); } type GroupProps = { searchName: string; group: ParameterGroup; uiState: any; onVisibilityChange: EventHandlers['setGroupVisibility']; children: React.ReactChild; } export function Group(props: GroupProps) { switch (props.group.displayType) { case 'ShowHide': return default: return
{props.children}
; } } function ShowHideGroup(props: GroupProps) { const { searchName, group, uiState: { isVisible }, onVisibilityChange } = props; return (
{isVisible ? props.children : null}
) } type ParameterListProps = { parameters: string[]; parameterMap: Record; parameterElements: Record; paramDependenciesUpdating: Record; } export function ParameterList(props: ParameterListProps) { const { parameters, parameterMap, parameterElements, paramDependenciesUpdating } = props; return (
{Seq.from(parameters) .map(paramName => parameterMap[paramName]) .map(parameter => ( { parameter.visibleHelp !== undefined &&
{safeHtml(parameter.visibleHelp)}
}
{parameterElements[parameter.name]}
))}
) } function ParameterHeading(props: { parameter: Parameter, paramDependencyUpdating: boolean }) { const { parameter, paramDependencyUpdating } = props; return (

{safeHtml(parameter.help)} {parameter.displayName} {paramDependencyUpdating && }

) } export function SubmitButton( props: { submissionMetadata: SubmissionMetadata, submitButtonText?: string, submitting: boolean, submissionDisabled?: boolean } ) { return props.submitting ?
: ; } export const getSubmitButtonText = (submissionMetadata: SubmissionMetadata, submitButtonText?: string) => submitButtonText ? submitButtonText : submissionMetadata.type === 'create-strategy' ? 'Get Answer' : submissionMetadata.type === 'edit-step' ? 'Revise Step' : 'Run Step'; interface SearchNameInputProps { customName?: string; handleCustomNameChange: TextboxChangeHandler; } export function SearchNameInput(props: SearchNameInputProps) { let { customName, handleCustomNameChange } = props; return (
Give this search strategy a custom name. The name will appear in the first step box (truncated to 15 characters).
); } interface WeightInputProps { weight?: string; handleWeightChange: TextboxChangeHandler; } export function WeightInput(props: WeightInputProps) { let { weight, handleWeightChange } = props; return (
Give this search a weight (for example 10, 200, -50, integer only). It will show in a column in your result. In a search strategy, unions and intersects will sum the weights, giving higher scores to items found in multiple searches. Default weight is 10.
); } export function QuestionDescription(props: { description?: string, }) { return !props.description ? null : (

Description

{safeHtml(props.description)}
); } interface WebServicesTutorialLinkProps { onClick: (event: React.MouseEvent) => void } function WebServicesTutorialLink(props: WebServicesTutorialLinkProps) { let { onClick } = props; return (
{ event.preventDefault(); onClick(event); }} title="Build a Web Services URL from this Search" className="wdk-ReactRouterLink wdk-RecordActionLink" replace={false}> Build a Web Services URL from this Search >>
); } interface SubmitSectionProps { className: string; customName?: string; searchName: string; paramValues: Record; weight?: string; handleCustomNameChange: TextboxChangeHandler; handleWeightChange: TextboxChangeHandler; submissionMetadata: SubmissionMetadata; submitting: boolean; submitButtonText?: string; submissionDisabled?: boolean; onClickWebservicesTutorialLink: (event: React.MouseEvent) => void; } export function SubmitSection(props: SubmitSectionProps) { let { className, customName, handleCustomNameChange, weight, handleWeightChange, submissionMetadata, submitting, submitButtonText, submissionDisabled, onClickWebservicesTutorialLink } = props; return (
{ submissionMetadata.type === 'create-strategy' && !submitting && }
); }