Commit c246813e authored by Filip Šenk's avatar Filip Šenk Committed by Marek Veselý
Browse files

Editor 0.19.1 - autofreefrom

parent d4b21c19
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
{
  "name": "@inject/editor",
  "version": "0.19.0",
  "version": "0.19.1",
  "description": "Editor module to Inject Exercise Platform",
  "main": "index.js",
  "license": "MIT",
+20 −0
Original line number Diff line number Diff line
@@ -119,9 +119,29 @@ export const QUESTIONNAIRE_QUESTION_FORM: Form = {
    tooltip: '',
    optional: true,
  },
  correctMilestones: {
    label: 'Correct Milestones',
    tooltip: '',
    optional: true,
  },
  incorrectMilestones: {
    label: 'Incorrect Milestones',
    tooltip: '',
    optional: true,
  },
  note: {
    label: 'Note',
    tooltip: '',
    optional: true,
  },
  correctAnswer: {
    label: 'Correct answer',
    tooltip: '',
    optional: false,
  },
  regex: {
    label: 'Reqex',
    tooltip: '',
    optional: true,
  },
}
+293 −0
Original line number Diff line number Diff line
import type { ButtonProps } from '@blueprintjs/core'
import {
  Button,
  Dialog,
  DialogBody,
  DialogFooter,
  Divider,
  InputGroup,
} from '@blueprintjs/core'
import { css } from '@emotion/css'
import type { ZustandSetterSingle } from '@inject/shared'
import {
  compute,
  computed,
  create,
  notify,
  ShallowGet,
  ShallowGetSet,
} from '@inject/shared'
import type { FC } from 'react'
import { memo, useCallback, useEffect, useState } from 'react'
import { GENERIC_CONTENT } from '../../../assets/generalContent'
import { QUESTIONNAIRE_QUESTION_FORM } from '../../../assets/pageContent/injectSpecification'
import { db } from '../../../indexeddb/db'
import {
  patchQuestionnaireMilestone,
  updateQuestionnaireQuestion,
} from '../../../indexeddb/operations'
import type {
  QuestionnaireQuestion,
  QuestionnaireQuestionAutoFreeForm,
} from '../../../indexeddb/types'
import TooltipLabel from '../../Tooltips/TooltipLabel'
import TooltipSwitch from '../../Tooltips/TooltipSwitch'
import QuestionnaireMilestoneForm from './QuestionnaireMilestoneForm'

interface QuestionnaireQuestionFormProps {
  questionnaireQuestion?: QuestionnaireQuestion
  autoFreeformQuestion?: QuestionnaireQuestionAutoFreeForm
  injectInfoId: number
  buttonProps: ButtonProps
}

type State<
  S = {
    text: string
    valid: boolean
    multiline: boolean
    note?: string
    update: (id?: number) => Promise<void>
    correctAnswer: string
    regex: boolean
  },
> = S & ZustandSetterSingle<S>

const AutoFreeformForm: FC<QuestionnaireQuestionFormProps> = ({
  questionnaireQuestion,
  autoFreeformQuestion,
  injectInfoId,
  buttonProps,
}) => {
  const [isOpen, setIsOpen] = useState(false)
  const state = create<State>(
    computed((set, get) => ({
      text: '',
      correctAnswer: '',
      multiline: false,
      note: '',
      regex: false,
      ...compute(get, state => ({
        valid: !!state.text && !!state.correctAnswer,
      })),
      set: (item, value) => set({ [item]: value }),
      update: async id => {
        const { multiline, text, note, correctAnswer, regex } = get()

        const correctMilestones = (
          await db.questionnaireMilestones
            .where({ injectInfoId })
            .and(
              ms =>
                (ms.questionId === id || ms.questionId === undefined) &&
                ms.correct === true
            )
            .toArray()
        ).map(it => it.id)

        const incorrectMilestones = (
          await db.questionnaireMilestones
            .where({ injectInfoId })
            .and(
              ms =>
                (ms.questionId === id || ms.questionId === undefined) &&
                ms.correct === false
            )
            .toArray()
        ).map(it => it.id)

        const newId = await updateQuestionnaireQuestion(
          {
            id,
            injectInfoId,
            type: 'auto-free-form',
            text,
            note,
          },
          {
            correctAnswer,
            multiline,
            regex,
            correctMilestones,
            incorrectMilestones,
          }
        )

        await patchQuestionnaireMilestone(
          [...correctMilestones, ...incorrectMilestones],
          injectInfoId,
          newId
        )
      },
    }))
  )

  useEffect(() => {
    state.setState({
      text: questionnaireQuestion?.text,
      multiline: autoFreeformQuestion?.multiline,
      note: questionnaireQuestion?.note,
      correctAnswer: autoFreeformQuestion?.correctAnswer,
      regex: autoFreeformQuestion?.regex,
    })
  }, [isOpen, state, questionnaireQuestion, autoFreeformQuestion])

  const handleUpdateButton = useCallback(async () => {
    try {
      state.getState().update(questionnaireQuestion?.id)
      setIsOpen(false)
    } catch (error) {
      const { text } = state.getState()
      notify(
        `Failed to update questionnaire question '${text}': ${error}`,
        JSON.stringify(error),
        {
          intent: 'danger',
        }
      )
    }
  }, [questionnaireQuestion?.id, state])

  return (
    <>
      <Button {...buttonProps} onClick={() => setIsOpen(true)} />
      <Dialog
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        icon={questionnaireQuestion ? 'edit' : 'plus'}
        title={
          questionnaireQuestion
            ? 'Edit questionnaire question'
            : 'New questionnaire question'
        }
      >
        <DialogBody>
          <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.text}>
            <ShallowGetSet
              store={state}
              get={({ text }) => text}
              set={({ set }) => set}
            >
              {(text, set) => (
                <InputGroup
                  placeholder='Input text'
                  value={text}
                  onChange={e => set('text', e.target.value)}
                />
              )}
            </ShallowGetSet>
          </TooltipLabel>
          <ShallowGetSet
            store={state}
            get={({ multiline }) => multiline}
            set={({ set }) => set}
          >
            {(multiline, set) => (
              <TooltipSwitch
                label={QUESTIONNAIRE_QUESTION_FORM.multiline}
                switchProps={{
                  checked: multiline,

                  onChange: () => set('multiline', !multiline),
                }}
              />
            )}
          </ShallowGetSet>
          <Divider
            className={css`
              margin-block: 1rem !important;
            `}
          />
          <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.correctAnswer}>
            <ShallowGetSet
              store={state}
              get={({ correctAnswer }) => correctAnswer}
              set={({ set }) => set}
            >
              {(correctAnswer, set) => (
                <InputGroup
                  placeholder='Input correct answer'
                  value={correctAnswer}
                  onChange={e => set('correctAnswer', e.target.value)}
                />
              )}
            </ShallowGetSet>
          </TooltipLabel>
          <ShallowGetSet
            store={state}
            get={({ regex }) => regex}
            set={({ set }) => set}
          >
            {(regex, set) => (
              <TooltipSwitch
                label={QUESTIONNAIRE_QUESTION_FORM.regex}
                switchProps={{
                  checked: regex,
                  onChange: () => set('regex', !regex),
                }}
              />
            )}
          </ShallowGetSet>
          <Divider
            className={css`
              margin-block: 1rem !important;
            `}
          />

          <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.note}>
            <ShallowGetSet
              store={state}
              get={({ note }) => note}
              set={({ set }) => set}
            >
              {(note, set) => (
                <InputGroup
                  placeholder='Input note'
                  value={note}
                  onChange={e => set('note', e.target.value)}
                />
              )}
            </ShallowGetSet>
          </TooltipLabel>

          <QuestionnaireMilestoneForm
            questionId={questionnaireQuestion?.id}
            injectInfoId={injectInfoId}
            title={QUESTIONNAIRE_QUESTION_FORM.correctMilestones.label}
            correct
            className={css`
              margin-bottom: 1rem;
            `}
          />
          <QuestionnaireMilestoneForm
            questionId={questionnaireQuestion?.id}
            injectInfoId={injectInfoId}
            title={QUESTIONNAIRE_QUESTION_FORM.incorrectMilestones.label}
            correct={false}
          />
        </DialogBody>
        <DialogFooter
          actions={
            <ShallowGet store={state} get={({ valid }) => valid}>
              {isValid => (
                <Button
                  disabled={!isValid}
                  onClick={() => handleUpdateButton()}
                  intent='primary'
                  icon={questionnaireQuestion ? 'edit' : 'plus'}
                  text={
                    questionnaireQuestion
                      ? GENERIC_CONTENT.buttons.save
                      : GENERIC_CONTENT.buttons.add
                  }
                />
              )}
            </ShallowGet>
          }
        />
      </Dialog>
    </>
  )
}

export default memo(AutoFreeformForm)
+5 −4
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import type {
} from '../../../indexeddb/types'
import TooltipLabel from '../../Tooltips/TooltipLabel'
import TooltipSwitch from '../../Tooltips/TooltipSwitch'
import RelatedMilestoneForm from './RelatedMilestoneForm'
import QuestionnaireMilestoneForm from './QuestionnaireMilestoneForm'

interface QuestionnaireQuestionFormProps {
  questionnaireQuestion?: QuestionnaireQuestion
@@ -99,9 +99,9 @@ const FreeformForm: FC<QuestionnaireQuestionFormProps> = ({

  useEffect(() => {
    state.setState({
      text: questionnaireQuestion?.text,
      text: questionnaireQuestion?.text ?? '',
      multiline: freeformQuestion?.multiline,
      note: questionnaireQuestion?.note,
      note: questionnaireQuestion?.note ?? '',
    })
  }, [isOpen, state, questionnaireQuestion, freeformQuestion])

@@ -166,9 +166,10 @@ const FreeformForm: FC<QuestionnaireQuestionFormProps> = ({
            )}
          </ShallowGetSet>

          <RelatedMilestoneForm
          <QuestionnaireMilestoneForm
            questionId={freeformQuestion?.id}
            injectInfoId={injectInfoId}
            title={QUESTIONNAIRE_QUESTION_FORM.relatedMilestones.label}
          />
          <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.note}>
            <ShallowGetSet
+125 −0
Original line number Diff line number Diff line
@@ -6,11 +6,11 @@ import {
  Icon,
  InputGroup,
} from '@blueprintjs/core'
import { css, cx } from '@emotion/css'
import { useLiveQuery } from 'dexie-react-hooks'
import { sortBy } from 'lodash'
import type { FC, FormEventHandler } from 'react'
import { memo, useRef } from 'react'
import { QUESTIONNAIRE_QUESTION_FORM } from '../../../assets/pageContent/injectSpecification'
import { db } from '../../../indexeddb/db'
import {
  addQuestionnaireMilestone,
@@ -31,7 +31,6 @@ const RelatedMilestoneAdder: FC<{
  }

  return (
    <>
    <form onSubmit={cb}>
      <ControlGroup fill>
        <InputGroup type='text' placeholder='Milestone name' inputRef={ref} />
@@ -40,26 +39,47 @@ const RelatedMilestoneAdder: FC<{
        </Button>
      </ControlGroup>
    </form>
    </>
  )
}

// TODO: make the state temporary and write only on onSave
const RelatedMilestoneForm: FC<{
const QuestionnaireMilestoneForm: FC<{
  questionId?: number
  injectInfoId: number
}> = ({ injectInfoId, questionId }) => {
  title: string
  correct?: boolean
  className?: string
}> = ({ injectInfoId, questionId, title, correct, className }) => {
  const milestones = useLiveQuery(() =>
    db.questionnaireMilestones
      .where({ injectInfoId })
      .and(ms => ms.questionId === questionId || ms.questionId === undefined)
      .and(
        ms =>
          (ms.questionId === questionId || ms.questionId === undefined) &&
          ms.correct === correct
      )
      .toArray()
  )

  return (
    <>
      <span style={{ flexGrow: '1', marginBottom: '0' }}>
        {QUESTIONNAIRE_QUESTION_FORM.relatedMilestones.label}
    <div
      className={cx(
        css`
          display: flex;
          flex-direction: column;
          margin-bottom: 1rem;
        `,
        className
      )}
    >
      <span
        className={css`
          padding-bottom: 0.375rem !important;
        `}
      >
        {title}
      </span>
      {milestones && milestones.length > 0 && (
        <CardList compact>
          {sortBy(milestones || [], ms => ms.name).map(
            ({ id, name, questionId }) => (
@@ -87,18 +107,19 @@ const RelatedMilestoneForm: FC<{
            )
          )}
        </CardList>
      )}

      <RelatedMilestoneAdder
        onAdd={name => {
          addQuestionnaireMilestone({
            name,
            injectInfoId,
            questionId,
            correct,
          })
        }}
      />
    </>
    </div>
  )
}

export default memo(RelatedMilestoneForm)
export default memo(QuestionnaireMilestoneForm)
Loading