Commit eb5f3eef authored by Adam Parák's avatar Adam Parák 💬
Browse files

Resolve "Refactor inject specification (editor) without the changed state"

parent de8ab292
Loading
Loading
Loading
Loading
+1 −2
Original line number Original line Diff line number Diff line
@@ -64,7 +64,6 @@ const DefinitionImportDialog: FC<DefinitionImportDialogProps> = ({
        intent: 'danger',
        intent: 'danger',
      })
      })
      await clearDb()
      await clearDb()
      clearDb()
      setConfig({})
      setConfig({})
      setGitlabConfig(prev => ({
      setGitlabConfig(prev => ({
        ...prev,
        ...prev,
@@ -72,7 +71,7 @@ const DefinitionImportDialog: FC<DefinitionImportDialogProps> = ({
        branchFrom: undefined,
        branchFrom: undefined,
      }))
      }))
    }
    }
  }, [onAdd])
  }, [nav, onAdd, setConfig, setGitlabConfig])


  return (
  return (
    <>
    <>
+5 −0
Original line number Original line Diff line number Diff line
@@ -3,6 +3,11 @@ import { useMemo } from 'react'
import { db } from '../../indexeddb/db'
import { db } from '../../indexeddb/db'
import { validateExpression } from '../../utils'
import { validateExpression } from '../../utils'


export const validateExpressionStatic = async (expression?: number[]) => {
  const milestones = await db.milestones.toArray()
  return validateExpression(expression || [], milestones)
}

const useValidateExpression = (expression?: number[]) => {
const useValidateExpression = (expression?: number[]) => {
  const milestones = useLiveQuery(() => db.milestones.toArray(), [], [])
  const milestones = useLiveQuery(() => db.milestones.toArray(), [], [])


+76 −80
Original line number Original line Diff line number Diff line
import { NumericInput } from '@blueprintjs/core'
import { NumericInput } from '@blueprintjs/core'
import { notify } from '@inject/shared'
import { ShallowGet, ShallowGetSet, useShallowStore } from '@inject/shared'
import { useLiveQuery } from 'dexie-react-hooks'
import { useLiveQuery } from 'dexie-react-hooks'
import { memo, useCallback, useEffect, useState, type FC } from 'react'
import { memo, useCallback, useEffect, type FC } from 'react'
import { INJECT_CONNECTIONS_FORM } from '../../assets/pageContent/injectSpecification'
import { INJECT_CONNECTIONS_FORM } from '../../assets/pageContent/injectSpecification'
import {
import { getInjectControlByInjectInfoId } from '../../indexeddb/operations'
  addInjectControl,
  getInjectControlByInjectInfoId,
  updateInjectControl,
} from '../../indexeddb/operations'
import type { InjectControl } from '../../indexeddb/types'
import type { InjectControl } from '../../indexeddb/types'
import { InjectType } from '../../indexeddb/types'
import { InjectType } from '../../indexeddb/types'
import { InjectsSpecificationRoute } from '../../routes/create/inject-specification.index'
import { InjectsSpecificationRoute } from '../../routes/create/inject-specification.index'
@@ -15,19 +11,24 @@ import ExpressionBuilder from '../ExpressionBuilder'
import useValidateExpression from '../ExpressionBuilder/useValidateExpression'
import useValidateExpression from '../ExpressionBuilder/useValidateExpression'
import SaveButtonGroup from '../SaveButtonGroup'
import SaveButtonGroup from '../SaveButtonGroup'
import TooltipLabel from '../Tooltips/TooltipLabel'
import TooltipLabel from '../Tooltips/TooltipLabel'
import type { InjectSpecState } from './store'


interface ConnectionsFormProps {
export interface ConnectionsInjectData {
  injectInfoId: number
  start: number
  injectType: InjectType
  delay: number
  changed: boolean
  milestoneCondition: number[]
  onChangedChange: (value: boolean) => void
}
}


const ConnectionsForm: FC<ConnectionsFormProps> = ({
type ConnectionsFormProps<T extends InjectType = InjectType> = FC<{
  injectInfoId: number
  injectType: T
  state: InjectSpecState<T>
}>

const ConnectionsForm: ConnectionsFormProps = ({
  injectInfoId,
  injectInfoId,
  injectType,
  injectType,
  changed,
  state,
  onChangedChange,
}) => {
}) => {
  const injectControl = useLiveQuery(
  const injectControl = useLiveQuery(
    () => getInjectControlByInjectInfoId(injectInfoId),
    () => getInjectControlByInjectInfoId(injectInfoId),
@@ -35,83 +36,78 @@ const ConnectionsForm: FC<ConnectionsFormProps> = ({
    null
    null
  ) as InjectControl
  ) as InjectControl


  const [start, setStart] = useState<number>(0)
  const milestoneConditions = useShallowStore(
  const [delay, setDelay] = useState<number>(0)
    state,
  const [milestoneCondition, setMilestoneCondition] = useState<number[]>([])
    state => state.milestoneCondition
  const { isValid } = useValidateExpression(milestoneCondition)
  )

  const { isValid } = useValidateExpression(milestoneConditions)
  useEffect(() => {
  useEffect(() => {
    setStart(injectControl?.start || 0)
    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
    injectType === InjectType.QUESTIONNAIRE
      ? setDelay(0)
      : setDelay(injectControl?.delay || 0)
    setMilestoneCondition(injectControl?.milestoneCondition || [])
  }, [injectControl, injectType])

  const update = useCallback(
    async (newInjectControl: InjectControl) => {
      try {
    if (injectControl) {
    if (injectControl) {
          await updateInjectControl({
      state.setState({
            ...newInjectControl,
        start: injectControl.start,
          })
        milestoneCondition: injectControl.milestoneCondition,
        } else {
        ...(injectType === InjectType.QUESTIONNAIRE
          await addInjectControl(newInjectControl)
          ? {
        }
              delay: 0,
      } catch (error) {
        notify(
          `Failed to update inject control: ${error}`,
          JSON.stringify(error),
          {
            intent: 'danger',
            }
            }
        )
          : {
              delay: injectControl.delay,
            }),
      })
    }
    }
    },
  }, [injectControl, injectType, state])
    [injectControl]
  )


  const handleUpdate = useCallback(async () => {
  const handleUpdate = useCallback(async () => {
    if (!changed) onChangedChange(true)
    state.getState().update(injectInfoId)
    await update({
  }, [injectInfoId, state])
      injectInfoId,
      start,
      delay,
      milestoneCondition: isValid ? milestoneCondition : [],
    })
    onChangedChange(false)
  }, [changed, injectInfoId, start, delay, milestoneCondition, isValid, update])

  useEffect(() => {
    if (changed) handleUpdate()
  }, [changed, handleUpdate])


  return (
  return (
    <div>
    <div>
      <TooltipLabel label={INJECT_CONNECTIONS_FORM.time}>
      <TooltipLabel label={INJECT_CONNECTIONS_FORM.time}>
        <ShallowGetSet
          store={state}
          get={state => state.start}
          set={state => state.set}
        >
          {(value, setValue) => (
            <NumericInput
            <NumericInput
              placeholder='Input number'
              placeholder='Input number'
              min={0}
              min={0}
          value={start}
              value={value}
          onValueChange={(value: number) => setStart(value)}
              onValueChange={(value: number) => setValue('start', value)}
            />
            />
          )}
        </ShallowGetSet>
      </TooltipLabel>
      </TooltipLabel>
      {injectType !== InjectType.QUESTIONNAIRE && (
      {injectType !== InjectType.QUESTIONNAIRE && (
        <TooltipLabel label={INJECT_CONNECTIONS_FORM.delay}>
        <TooltipLabel label={INJECT_CONNECTIONS_FORM.delay}>
          <ShallowGetSet
            store={state}
            get={state => state.delay}
            set={state => state.set}
          >
            {(value, setValue) => (
              <NumericInput
              <NumericInput
                placeholder='Input number'
                placeholder='Input number'
                min={0}
                min={0}
            value={delay}
                value={value}
            onValueChange={(value: number) => setDelay(value)}
                onValueChange={(value: number) => setValue('delay', value)}
              />
              />
            )}
          </ShallowGetSet>
        </TooltipLabel>
        </TooltipLabel>
      )}
      )}
      <ShallowGet store={state} get={state => state.set}>
        {setValue => (
          <ExpressionBuilder
          <ExpressionBuilder
            label={INJECT_CONNECTIONS_FORM.condition}
            label={INJECT_CONNECTIONS_FORM.condition}
            initExpression={injectControl?.milestoneCondition}
            initExpression={injectControl?.milestoneCondition}
        onExpressionChange={expression => setMilestoneCondition(expression)}
            onExpressionChange={expression =>
              setValue('milestoneCondition', expression)
            }
          />
          />
        )}
      </ShallowGet>
      <SaveButtonGroup
      <SaveButtonGroup
        isValid={isValid}
        isValid={isValid}
        handleUpdate={() => handleUpdate()}
        handleUpdate={() => handleUpdate()}
+117 −117
Original line number Original line Diff line number Diff line
import { InputGroup, NumericInput, TextArea } from '@blueprintjs/core'
import { InputGroup, NumericInput, TextArea } from '@blueprintjs/core'
import { notify } from '@inject/shared'
import { ShallowGetSet } from '@inject/shared'
import { useLiveQuery } from 'dexie-react-hooks'
import { useLiveQuery } from 'dexie-react-hooks'
import { memo, useCallback, useEffect, useState, type FC } from 'react'
import { useCallback, useEffect, type FC } from 'react'
import { EMAIL_ADDRESS_FORM } from '../../assets/pageContent/emails'
import { EMAIL_ADDRESS_FORM } from '../../assets/pageContent/emails'
import { EMAIL_INJECT_FORM } from '../../assets/pageContent/injectSpecification'
import { EMAIL_INJECT_FORM } from '../../assets/pageContent/injectSpecification'
import {
import { getEmailInjectByInjectInfoId } from '../../indexeddb/operations'
  addEmailInject,
import type { EmailInject, InjectType } from '../../indexeddb/types'
  getEmailInjectByInjectInfoId,
  updateEmailInject,
} from '../../indexeddb/operations'
import type { EmailInject } from '../../indexeddb/types'
import { InjectsSpecificationRoute } from '../../routes/create/inject-specification.index'
import { InjectsSpecificationRoute } from '../../routes/create/inject-specification.index'
import { SelectChangeShared } from '../../utils'
import EmailAddressSelector from '../EmailAddressSelector'
import EmailAddressSelector from '../EmailAddressSelector'
import FileSelector from '../FileSelector'
import FileSelector from '../FileSelector'
import SaveButtonGroup from '../SaveButtonGroup'
import SaveButtonGroup from '../SaveButtonGroup'
import TooltipLabel from '../Tooltips/TooltipLabel'
import TooltipLabel from '../Tooltips/TooltipLabel'
import type { InjectSpecState } from './store'


interface EmailInjectFormProps {
export interface EmailInjectData {
  injectInfoId: number
  subject: string
  changed: boolean
  content: string
  onChangedChange: (value: boolean) => void
  selectedAddressId: number
  extraCopies: number
  fileId: number
}
}


const EmailInjectForm: FC<EmailInjectFormProps> = ({
const EmailInjectForm: FC<{
  injectInfoId,
  injectInfoId: number
  changed,
  state: InjectSpecState<InjectType.EMAIL>
  onChangedChange,
}> = ({ injectInfoId, state }) => {
}) => {
  const emailInject = useLiveQuery(
  const emailInject = useLiveQuery(
    () => getEmailInjectByInjectInfoId(injectInfoId),
    () => getEmailInjectByInjectInfoId(injectInfoId),
    [injectInfoId],
    [injectInfoId],
    null
    null
  ) as EmailInject
  ) as EmailInject


  const [subject, setSubject] = useState<string>('')
  const [content, setContent] = useState<string>('')
  const [selectedAddressId, setSelectedAddressId] = useState<number>(0)
  const [extraCopies, setExtraCopies] = useState<number>(0)
  const [fileId, setFileId] = useState<number>(0)

  useEffect(() => {
  useEffect(() => {
    setSubject(emailInject?.subject || '')
    setContent(emailInject?.content || '')
    setSelectedAddressId(emailInject?.emailAddressId || 0)
    setExtraCopies(emailInject?.extraCopies || 0)
    setFileId(emailInject?.fileId || 0)
  }, [emailInject])

  const updateInject = useCallback(
    async (newEmailInject: EmailInject) => {
      try {
    if (emailInject) {
    if (emailInject) {
          await updateEmailInject({ ...newEmailInject })
      state.setState({
        } else {
        subject: emailInject.subject,
          await addEmailInject(newEmailInject)
        content: emailInject.content,
        }
        selectedAddressId: emailInject.emailAddressId,
      } catch (error) {
        extraCopies: emailInject.extraCopies,
        notify(
        fileId: emailInject.fileId,
          `Failed to update email inject: ${error}`,
          JSON.stringify(error),
          {
            intent: 'danger',
          }
        )
      }
    },
    [emailInject]
  )

  const handleUpdate = useCallback(async () => {
    if (!changed) onChangedChange(true)
    await updateInject({
      injectInfoId,
      subject,
      content,
      emailAddressId: selectedAddressId,
      extraCopies,
      fileId,
      })
      })
    onChangedChange(false)
    }
  }, [
  }, [emailInject, state])
    changed,
    injectInfoId,
    subject,
    content,
    selectedAddressId,
    extraCopies,
    fileId,
    updateInject,
  ])


  useEffect(() => {
  const handleUpdate = useCallback(() => {
    if (changed) handleUpdate()
    state.getState().update(injectInfoId)
  }, [changed, handleUpdate])
  }, [state, injectInfoId])


  return (
  return (
    <div>
    <div>
      <ShallowGetSet
        store={state}
        get={state => state.selectedAddressId}
        set={state => state.set}
      >
        {(value, setValue) => (
          <EmailAddressSelector
          <EmailAddressSelector
            label={EMAIL_ADDRESS_FORM.address}
            label={EMAIL_ADDRESS_FORM.address}
        emailAddressId={selectedAddressId}
            emailAddressId={value}
        onChange={id =>
            onChange={id => {
          SelectChangeShared({ setValue: setSelectedAddressId, value: id })
              if (value === id) {
                setValue('selectedAddressId', 0)
              }
              }
              setValue('selectedAddressId', id)
            }}
          />
          />
        )}
      </ShallowGetSet>
      <ShallowGetSet
        store={state}
        get={state => state.subject}
        set={state => state.set}
      >
        {(value, setValue) => (
          <TooltipLabel label={EMAIL_INJECT_FORM.subject}>
          <TooltipLabel label={EMAIL_INJECT_FORM.subject}>
            <InputGroup
            <InputGroup
              placeholder='Input text'
              placeholder='Input text'
          value={subject}
              value={value}
          onChange={e => setSubject(e.target.value)}
              onChange={e => setValue('subject', e.target.value)}
            />
            />
          </TooltipLabel>
          </TooltipLabel>
        )}
      </ShallowGetSet>
      <ShallowGetSet
        store={state}
        get={state => state.content}
        set={state => state.set}
      >
        {(value, setValue) => (
          <TooltipLabel label={EMAIL_INJECT_FORM.content}>
          <TooltipLabel label={EMAIL_INJECT_FORM.content}>
            <TextArea
            <TextArea
          value={content}
              value={value}
              style={{
              style={{
                width: '100%',
                width: '100%',
                height: '10rem',
                height: '10rem',
@@ -121,22 +98,45 @@ const EmailInjectForm: FC<EmailInjectFormProps> = ({
                overflowY: 'auto',
                overflowY: 'auto',
              }}
              }}
              placeholder='Input text'
              placeholder='Input text'
          onChange={e => setContent(e.target.value)}
              onChange={e => setValue('content', e.target.value)}
            />
            />
          </TooltipLabel>
          </TooltipLabel>
        )}
      </ShallowGetSet>
      <ShallowGetSet
        store={state}
        get={state => state.extraCopies}
        set={state => state.set}
      >
        {(value, setValue) => (
          <TooltipLabel label={EMAIL_INJECT_FORM.extraCopies}>
          <TooltipLabel label={EMAIL_INJECT_FORM.extraCopies}>
            <NumericInput
            <NumericInput
              placeholder='Input number'
              placeholder='Input number'
              min={0}
              min={0}
          value={extraCopies}
              value={value}
          onValueChange={(value: number) => setExtraCopies(value)}
              onValueChange={(value: number) => setValue('extraCopies', value)}
            />
            />
          </TooltipLabel>
          </TooltipLabel>
        )}
      </ShallowGetSet>
      <ShallowGetSet
        store={state}
        get={state => state.fileId}
        set={state => state.set}
      >
        {(value, setValue) => (
          <FileSelector
          <FileSelector
            label={EMAIL_INJECT_FORM.file}
            label={EMAIL_INJECT_FORM.file}
        fileId={fileId}
            fileId={value}
        onChange={id => SelectChangeShared({ setValue: setFileId, value: id })}
            onChange={id => {
              if (value === id) {
                setValue('fileId', 0)
              }
              setValue('fileId', id)
            }}
          />
          />
        )}
      </ShallowGetSet>
      <SaveButtonGroup
      <SaveButtonGroup
        isValid
        isValid
        handleUpdate={() => handleUpdate()}
        handleUpdate={() => handleUpdate()}
@@ -146,4 +146,4 @@ const EmailInjectForm: FC<EmailInjectFormProps> = ({
  )
  )
}
}


export default memo(EmailInjectForm)
export default EmailInjectForm
+62 −80
Original line number Original line Diff line number Diff line
import { TextArea } from '@blueprintjs/core'
import { TextArea } from '@blueprintjs/core'
import { notify } from '@inject/shared'
import { ShallowGetSet } from '@inject/shared'
import { useLiveQuery } from 'dexie-react-hooks'
import { useLiveQuery } from 'dexie-react-hooks'
import { memo, useCallback, useEffect, useState, type FC } from 'react'
import { useCallback, useEffect, type FC } from 'react'
import { INFORMATION_INJECT_FORM } from '../../assets/pageContent/injectSpecification'
import { INFORMATION_INJECT_FORM } from '../../assets/pageContent/injectSpecification'
import {
import { getInformationInjectByInjectInfoId } from '../../indexeddb/operations'
  addInformationInject,
import type { InjectType } from '../../indexeddb/types'
  getInformationInjectByInjectInfoId,
  updateInformationInject,
} from '../../indexeddb/operations'
import type { InformationInject } from '../../indexeddb/types'
import { InjectsSpecificationRoute } from '../../routes/create/inject-specification.index'
import { InjectsSpecificationRoute } from '../../routes/create/inject-specification.index'
import { SelectChangeShared } from '../../utils'
import FileSelector from '../FileSelector'
import FileSelector from '../FileSelector'
import SaveButtonGroup from '../SaveButtonGroup'
import SaveButtonGroup from '../SaveButtonGroup'
import TooltipLabel from '../Tooltips/TooltipLabel'
import TooltipLabel from '../Tooltips/TooltipLabel'
import type { InjectSpecState } from './store'


interface InformationInjectFormProps {
export interface InformationInjectData {
  injectInfoId: number
  content: string
  changed: boolean
  fileId: number
  onChangedChange: (value: boolean) => void
}
}


const InformationInjectForm: FC<InformationInjectFormProps> = ({
const InformationInjectForm: FC<{
  injectInfoId,
  injectInfoId: number
  changed,
  state: InjectSpecState<InjectType.INFORMATION>
  onChangedChange,
}> = ({ injectInfoId, state }) => {
}) => {
  const informationInject = useLiveQuery(
  const informationInject = useLiveQuery(
    () => getInformationInjectByInjectInfoId(injectInfoId),
    () => getInformationInjectByInjectInfoId(injectInfoId),
    [injectInfoId],
    [injectInfoId]
    null
  )
  ) as InformationInject

  const [content, setContent] = useState<string>('')
  const [fileId, setFileId] = useState<number>(0)


  useEffect(() => {
  useEffect(() => {
    setContent(informationInject?.content || '')
    setFileId(informationInject?.fileId || 0)
  }, [informationInject])

  const updateInject = useCallback(
    async (newInformationInject: InformationInject) => {
      try {
    if (informationInject) {
    if (informationInject) {
          await updateInformationInject({
      state.setState({
            ...newInformationInject,
        content: informationInject?.content,
        fileId: informationInject?.fileId,
      })
      })
        } else {
          await addInformationInject(newInformationInject)
        }
      } catch (error) {
        notify(
          `Failed to update information inject: ${error}`,
          JSON.stringify(error),
          {
            intent: 'danger',
          }
        )
    }
    }
    },
  }, [informationInject, state])
    [informationInject]
  )


  const handleUpdate = useCallback(async () => {
  const handleUpdate = useCallback(() => {
    if (!changed) onChangedChange(true)
    state.getState().update(injectInfoId)
    await updateInject({
  }, [state, injectInfoId])
      injectInfoId,
      content,
      fileId,
    })
    onChangedChange(false)
  }, [changed, injectInfoId, content, fileId, updateInject])

  useEffect(() => {
    if (changed) handleUpdate()
  }, [changed, handleUpdate])


  return (
  return (
    <div>
    <div>
      <TooltipLabel label={INFORMATION_INJECT_FORM.content}>
      <TooltipLabel label={INFORMATION_INJECT_FORM.content}>
        <ShallowGetSet
          store={state}
          get={state => state.content}
          set={state => state.set}
        >
          {(value, setValue) => (
            <TextArea
            <TextArea
          value={content}
              value={value}
              style={{
              style={{
                width: '100%',
                width: '100%',
                height: '10rem',
                height: '10rem',
@@ -89,14 +56,29 @@ const InformationInjectForm: FC<InformationInjectFormProps> = ({
                overflowY: 'auto',
                overflowY: 'auto',
              }}
              }}
              placeholder='Input text'
              placeholder='Input text'
          onChange={e => setContent(e.target.value)}
              onChange={e => setValue('content', e.target.value)}
            />
            />
          )}
        </ShallowGetSet>
      </TooltipLabel>
      </TooltipLabel>
      <ShallowGetSet
        store={state}
        get={state => state.fileId}
        set={state => state.set}
      >
        {(value, setValue) => (
          <FileSelector
          <FileSelector
            label={INFORMATION_INJECT_FORM.file}
            label={INFORMATION_INJECT_FORM.file}
        fileId={fileId}
            fileId={value}
        onChange={id => SelectChangeShared({ setValue: setFileId, value: id })}
            onChange={id => {
              if (value === id) {
                setValue('fileId', 0)
              }
              setValue('fileId', id)
            }}
          />
          />
        )}
      </ShallowGetSet>
      <SaveButtonGroup
      <SaveButtonGroup
        isValid
        isValid
        handleUpdate={() => handleUpdate()}
        handleUpdate={() => handleUpdate()}
@@ -106,4 +88,4 @@ const InformationInjectForm: FC<InformationInjectFormProps> = ({
  )
  )
}
}


export default memo(InformationInjectForm)
export default InformationInjectForm
Loading