Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JN-1453] Add freetext editor to survey question designer #1349

Merged
merged 3 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions ui-admin/src/forms/designer/split/FormElementFreetextEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Question, QuestionType } from '@juniper/ui-core'
import React, { useState } from 'react'
import { Textarea } from 'components/forms/Textarea'
import { questionFromRawText } from '../../../util/juniperSurveyUtils'

export const FormElementFreetextEditor = ({ question, onChange }: {
question: Question, onChange: (newQuestion: Question) => void
}) => {
const [freetext, setFreetext] = useState<string>('')

return <Textarea
className="form-control mb-3 p-2"
value={freetext}
rows={15}
onChange={value => {
setFreetext(value)
const newQuestionObj = questionFromRawText(value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, crazy that this function already existed!

const newType = newQuestionObj.type as QuestionType
//questionFromRawText can only reasonably predict the type of question, it's choices, and it's text.
//We'll pick those three fields out and allow the user to decide the rest of the fields explicitly.
onChange({
...question,
type: newType,
title: newQuestionObj.title || '',
choices: newQuestionObj.choices
} as Question)
}}
label={'Freetext'}
labelClassname={'mb-0'}
infoContent={`Paste in question text to automatically generate a survey question. For text questions,
simply paste in the text. For radio and dropdown questions, paste the question text followed by
a new option on each line.`}
/>
}
34 changes: 25 additions & 9 deletions ui-admin/src/forms/designer/split/SplitFormElementDesigner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { isEqual } from 'lodash'
import { FormElementEditor } from './FormElementEditor'
import { FormElementJsonEditor } from './FormElementJsonEditor'
import { FormElementOptions } from './controls/FormElementOptions'
import { FormElementFreetextEditor } from './FormElementFreetextEditor'

/* Note that this component is memoized using React.memo
* Since survey pages can contain many elements, we need to be mindful of
Expand All @@ -31,6 +32,7 @@ export const SplitFormElementDesigner = memo(({
editedContent: FormContent, onChange: (newContent: FormContent) => void
}) => {
const [showJsonEditor, setShowJsonEditor] = useState(false)
const [showFreetextMode, setShowFreetextMode] = useState(false)

// Chop the survey down to just the specific question that we're editing, so we can display
// a preview using the SurveyJS survey component.
Expand All @@ -51,13 +53,35 @@ export const SplitFormElementDesigner = memo(({
<FormElementOptions
showJsonEditor={showJsonEditor}
setShowJsonEditor={setShowJsonEditor}
showFreetextMode={showFreetextMode}
setShowFreetextMode={setShowFreetextMode}
elementIndex={elementIndex}
element={element}
currentPageNo={currentPageNo}
editedContent={editedContent}
onChange={onChange}
/>
{ !showJsonEditor ?
{ showFreetextMode &&
<FormElementFreetextEditor
question={element as Question}
onChange={newQuestion => {
const newContent = { ...editedContent }
newContent.pages[currentPageNo].elements[elementIndex] = newQuestion
onChange(newContent)
}}
/>
}
{ showJsonEditor &&
<FormElementJsonEditor
question={element as Question}
onChange={newQuestion => {
const newContent = { ...editedContent }
newContent.pages[currentPageNo].elements[elementIndex] = newQuestion
onChange(newContent)
}}
/>
}
{ !showJsonEditor && !showFreetextMode &&
<FormElementEditor
element={element}
elementIndex={elementIndex}
Expand All @@ -66,14 +90,6 @@ export const SplitFormElementDesigner = memo(({
onChange={onChange}
currentLanguage={currentLanguage}
supportedLanguages={supportedLanguages}
/> :
<FormElementJsonEditor
question={element as Question}
onChange={newQuestion => {
const newContent = { ...editedContent }
newContent.pages[currentPageNo].elements[elementIndex] = newQuestion
onChange(newContent)
}}
/>
}
</div>
Expand Down
19 changes: 17 additions & 2 deletions ui-admin/src/forms/designer/split/controls/FormElementOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from 'react'
import { IconButton } from 'components/forms/Button'
import { faClone, faCode } from '@fortawesome/free-solid-svg-icons'
import { faClone, faCode, faKeyboard, faWandMagicSparkles } from '@fortawesome/free-solid-svg-icons'
import { ListElementController } from 'portal/siteContent/designer/components/ListElementController'
import { FormContent, FormElement } from '@juniper/ui-core'

type FormElementOptionsProps = {
showJsonEditor: boolean,
setShowJsonEditor: (show: boolean) => void,
showFreetextMode: boolean,
setShowFreetextMode: (show: boolean) => void,
elementIndex: number,
element: FormElement,
currentPageNo: number,
Expand All @@ -17,6 +19,8 @@ type FormElementOptionsProps = {
export const FormElementOptions = ({
showJsonEditor,
setShowJsonEditor,
showFreetextMode,
setShowFreetextMode,
elementIndex,
element,
currentPageNo,
Expand All @@ -27,9 +31,20 @@ export const FormElementOptions = ({
<div className="d-flex justify-content-end">
<div className="d-flex border rounded-3 rounded-top-0 border-top-0 bg-light">
<IconButton icon={faCode}
aria-label={showJsonEditor ? 'Switch to designer' : 'Switch to JSON editor'}
disabled={showFreetextMode}
aria-label={
showJsonEditor ?
'Switch to designer' :
showFreetextMode ? 'You must exit the freetext editor to switch to JSON editor' :
'Switch to JSON editor'
}
onClick={() => setShowJsonEditor(!showJsonEditor)}
/>
<IconButton icon={showFreetextMode ? faKeyboard : faWandMagicSparkles}
disabled={showJsonEditor}
aria-label={showFreetextMode ? 'Switch to designer' : 'Switch to freetext editor'}
onClick={() => setShowFreetextMode(!showFreetextMode)}
/>
Comment on lines +43 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seemed to be some weirdness when I entered freetext mode in a panel where it didn't properly create a stableid and I ended up getting an error preventing me from saving.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how it behaves with regular questions too. We could auto-generate a question stable id, but since it's an identifier for research/operational datasets I think we should force the user to type one out. Of course, always open to changing later if we hear that it's annoying users!

<IconButton icon={faClone}
aria-label={'Clone'}
onClick={() => {
Expand Down
Loading