Loading editor/src/components/DefinitionImportDialog/index.tsx +1 −2 Original line number Original line Diff line number Diff line Loading @@ -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, Loading @@ -72,7 +71,7 @@ const DefinitionImportDialog: FC<DefinitionImportDialogProps> = ({ branchFrom: undefined, branchFrom: undefined, })) })) } } }, [onAdd]) }, [nav, onAdd, setConfig, setGitlabConfig]) return ( return ( <> <> Loading editor/src/components/ExpressionBuilder/useValidateExpression.tsx +5 −0 Original line number Original line Diff line number Diff line Loading @@ -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(), [], []) Loading editor/src/components/InjectSpecification/ConnectionsForm.tsx +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' Loading @@ -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), Loading @@ -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()} Loading editor/src/components/InjectSpecification/EmailInjectForm.tsx +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', Loading @@ -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()} Loading @@ -146,4 +146,4 @@ const EmailInjectForm: FC<EmailInjectFormProps> = ({ ) ) } } export default memo(EmailInjectForm) export default EmailInjectForm editor/src/components/InjectSpecification/InformationInjectForm.tsx +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', Loading @@ -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()} Loading @@ -106,4 +88,4 @@ const InformationInjectForm: FC<InformationInjectFormProps> = ({ ) ) } } export default memo(InformationInjectForm) export default InformationInjectForm Loading
editor/src/components/DefinitionImportDialog/index.tsx +1 −2 Original line number Original line Diff line number Diff line Loading @@ -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, Loading @@ -72,7 +71,7 @@ const DefinitionImportDialog: FC<DefinitionImportDialogProps> = ({ branchFrom: undefined, branchFrom: undefined, })) })) } } }, [onAdd]) }, [nav, onAdd, setConfig, setGitlabConfig]) return ( return ( <> <> Loading
editor/src/components/ExpressionBuilder/useValidateExpression.tsx +5 −0 Original line number Original line Diff line number Diff line Loading @@ -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(), [], []) Loading
editor/src/components/InjectSpecification/ConnectionsForm.tsx +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' Loading @@ -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), Loading @@ -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()} Loading
editor/src/components/InjectSpecification/EmailInjectForm.tsx +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', Loading @@ -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()} Loading @@ -146,4 +146,4 @@ const EmailInjectForm: FC<EmailInjectFormProps> = ({ ) ) } } export default memo(EmailInjectForm) export default EmailInjectForm
editor/src/components/InjectSpecification/InformationInjectForm.tsx +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', Loading @@ -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()} Loading @@ -106,4 +88,4 @@ const InformationInjectForm: FC<InformationInjectFormProps> = ({ ) ) } } export default memo(InformationInjectForm) export default InformationInjectForm