Skip to content

Commit

Permalink
Implement individuals form (WiP)
Browse files Browse the repository at this point in the history
  • Loading branch information
khoidt committed Mar 11, 2024
1 parent 6ace461 commit 6ade26c
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 139 deletions.
76 changes: 52 additions & 24 deletions src/fragmentarium/ui/fragment/ColophonEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { Form, Button, Row } from 'react-bootstrap'
import { Provenance } from 'corpus/domain/provenance'
import { Fragment } from 'fragmentarium/domain/fragment'
import {
ColophonIndividualsInput,
ColophonOwnershipInput,
ColophonStatusInput,
ColophonTypeInput,
ColophonOriginalFromInput,
ColophonWrittenInInput,
} from './ColophonEditorInputs'
import FragmentService from 'fragmentarium/application/FragmentService'
import { ColophonIndividualsInput } from './ColophonEditorIndividualInput'
import produce, { Draft, castDraft, immerable } from 'immer'

export enum ColophonStatus {
Yes = 'Yes',
Expand Down Expand Up @@ -62,30 +63,55 @@ export enum IndividualType {
}

export interface NameAttestation {
value: string
isBroken: boolean
isUncertain: boolean
value?: string
isBroken?: boolean
isUncertain?: boolean
}

export interface ProvenanceAttestation {
value: Provenance
isBroken: boolean
isUncertain: boolean
value?: Provenance
isBroken?: boolean
isUncertain?: boolean
}

export interface IndividualTypeAttestation {
value: IndividualType
isBroken: boolean
isUncertain: boolean
value?: IndividualType
isBroken?: boolean
isUncertain?: boolean
}

export interface Individual {
name?: NameAttestation
sonOf?: NameAttestation
grandsonOf?: NameAttestation
family?: NameAttestation
nativeOf?: ProvenanceAttestation
type?: IndividualType
export class IndividualAttestation {
readonly [immerable] = true

constructor(
readonly name?: NameAttestation,
readonly sonOf?: NameAttestation,
readonly grandsonOf?: NameAttestation,
readonly family?: NameAttestation,
readonly nativeOf?: ProvenanceAttestation,
readonly type?: IndividualType
) {}

setType(type?: IndividualType): IndividualAttestation {
return produce(this, (draft: Draft<IndividualAttestation>) => {
draft.type = castDraft(type)
})
}

setNameField(
field: 'name' | 'sonOf' | 'grandsonOf' | 'family',
name?: NameAttestation
): IndividualAttestation {
return produce(this, (draft: Draft<IndividualAttestation>) => {
draft[field] = castDraft(name)
})
}

setNativeOf(provenance?: ProvenanceAttestation): IndividualAttestation {
return produce(this, (draft: Draft<IndividualAttestation>) => {
draft.nativeOf = castDraft(provenance)
})
}
}

export interface Colophon {
Expand All @@ -95,7 +121,7 @@ export interface Colophon {
originalFrom?: ProvenanceAttestation
writtenIn?: ProvenanceAttestation
notesToScribalProcess?: string
individuals?: Individual[]
individuals?: IndividualAttestation[]
}

interface Props {
Expand Down Expand Up @@ -186,12 +212,14 @@ const ColophonEditor: React.FC<Props> = ({
fragmentService={fragmentService}
/>
</Row>
<Row>
<ColophonIndividualsInput
individuals={formData.individuals ?? []}
onChange={handleSelectChange('colophonType')}
/>
</Row>
<ColophonIndividualsInput
individuals={formData.individuals}
onChange={handleSelectChange}
searchIndividuals={() => {
const empty: readonly IndividualAttestation[] = []
return new Promise(() => empty)
}}
/>
<Button
variant="primary"
type="submit"
Expand Down
115 changes: 115 additions & 0 deletions src/fragmentarium/ui/fragment/ColophonEditorIndividualInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react'
import { Form, Row } from 'react-bootstrap'
import { IndividualAttestation, IndividualType } from './ColophonEditor'
import _ from 'lodash'
import ListForm from 'common/List'
import { BrokenAndUncertainSwitches } from 'common/BrokenAndUncertain'
import Select from 'react-select'

export const ColophonIndividualsInput = ({
searchIndividuals,
onChange,
label = 'Individuals',
collapsed = false,
individuals = [new IndividualAttestation()],
}: {
searchIndividuals: (
query: string
) => Promise<ReadonlyArray<IndividualAttestation>>
onChange
label?: string
collapsed?: boolean
individuals?: ReadonlyArray<IndividualAttestation>
}): JSX.Element => {
const _individuals =
individuals.length > 0 ? individuals : [new IndividualAttestation()]
return (
<ListForm
value={_individuals}
onChange={onChange}
label={label}
noun="Individual"
defaultValue={_individuals}
collapsed={collapsed}
>
{() =>
_individuals.map((individual, index) => {
return (
<IndividualForm
searchIndividuals={searchIndividuals}
onChange={onChange}
individual={individual}
key={index}
/>
)
})
}
</ListForm>
)
}

type IndividualFieldName = 'type' | 'name' | 'sonOf' | 'grandsonOf' | 'family'

const getValueAndOptionsBykey = (
key: IndividualFieldName,
individual: IndividualAttestation
): {
options: readonly { value: string; label: string }[]
value: { value: string; label: string }
} =>
key === 'type'
? {
value: { value: individual[key] ?? '', label: individual[key] ?? '' },
options: Object.values(IndividualType).map((type) => ({
value: type ?? '',
label: type ?? '',
})),
}
: {
value: {
value: individual[key]?.value ?? '',
label: individual[key]?.value ?? '',
},
options: [],
}

const IndividualForm = ({
searchIndividuals,
onChange,
individual,
}: {
searchIndividuals: (
query: string
) => Promise<readonly IndividualAttestation[]>
onChange
individual: IndividualAttestation
}): JSX.Element => {
const individualFields = [
'type',
'name',
'sonOf',
'grandsonOf',
'family',
].map((key) => (
<Form.Group key={`${key}-col`}>
<Form.Label>{_.startCase(key)}</Form.Label>
<Select
onChange={onChange}
isClearable={true}
key={key}
placeholder={_.startCase(key)}
{...getValueAndOptionsBykey(key as IndividualFieldName, individual)}
/>
{key !== 'type' && (
<Row key={`${key}-row`}>
<BrokenAndUncertainSwitches
key={`${key}-broken-uncertain`}
name={key}
{...individual[key]}
/>
</Row>
)}
</Form.Group>
))
return <>{individualFields}</>
}
108 changes: 4 additions & 104 deletions src/fragmentarium/ui/fragment/ColophonEditorInputs.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React from 'react'
import { Form, Col, Button, Row } from 'react-bootstrap'
import { Form, Col, Row } from 'react-bootstrap'
import Select from 'react-select'
import {
ColophonOwnership,
ColophonStatus,
ColophonType,
Individual,
IndividualType,
ProvenanceAttestation,
} from './ColophonEditor'
import _ from 'lodash'
Expand All @@ -31,7 +29,7 @@ const ProvenanceAttestationInput = ({
}): JSX.Element => {
return (
<Form.Group as={Col}>
<Form.Label>Written in</Form.Label>
<Form.Label>{_.startCase(name)}</Form.Label>
<ProvenanceSearchForm
fragmentService={fragmentService}
onChange={onChange}
Expand Down Expand Up @@ -65,7 +63,7 @@ export const ColophonStatusInput = ({
options={options}
value={{ value: colophonStatus, label: colophonStatus }}
onChange={onChange('colophonStatus')}
isClearable={false}
isClearable={true}
/>
</Form.Group>
)
Expand Down Expand Up @@ -94,7 +92,7 @@ export const ColophonOwnershipInput = ({
},
]}
onChange={onChange('colophonOwnership')}
isClearable={false}
isClearable={true}
/>
</Form.Group>
)
Expand Down Expand Up @@ -177,101 +175,3 @@ export const ColophonWrittenInInput = ({
/>
)
}

export const ColophonIndividualsInput = ({
individuals,
onChange,
}: {
individuals: Individual[]
onChange: (newIndividuals: Individual[]) => void
}): JSX.Element => {
const handleAddIndividual = () => {
onChange([
...individuals,
{
name: { value: '', isBroken: false, isUncertain: false },
sonOf: { value: '', isBroken: false, isUncertain: false },
grandsonOf: { value: '', isBroken: false, isUncertain: false },
family: { value: '', isBroken: false, isUncertain: false },
nativeOf: undefined,
type: IndividualType.Owner,
},
])
}

const handleRemoveIndividual = (index: number) => {
const updatedIndividuals = individuals.filter((_, i) => i !== index)
onChange(updatedIndividuals)
}

const updateIndividual = (
index: number,
field: keyof Individual,
key: string,
value: any
): void => {
const updatedIndividuals = [...individuals]
const updatedField = { ...updatedIndividuals[index], [key]: value }
updatedIndividuals[index] = {
...updatedIndividuals[index],
[field]: updatedField,
}
onChange(updatedIndividuals)
}

return (
<div>
{individuals.map((individual, index) => (
<IndividualInput
key={index}
individual={individual}
onUpdate={(field, key, value) =>
updateIndividual(index, field, key, value)
}
onRemove={() => handleRemoveIndividual(index)}
/>
))}
<Button variant="secondary" onClick={handleAddIndividual}>
Add Individual
</Button>
</div>
)
}

const IndividualInput = ({
individual,
onUpdate,
onRemove,
}: {
individual: Individual
onUpdate: (field: keyof Individual, key: string, value: any) => void
onRemove: () => void
}): JSX.Element => {
const inputFields = [
'type',
'name',
'sonOf',
'grandsonOf',
'family',
].map((key) => (
<Form.Control
type="text"
key={key}
placeholder={_.startCase(key)}
value={key === 'type' ? individual.type : individual[key]?.value}
onChange={(event) =>
onUpdate(key as keyof Individual, 'value', event.target.value)
}
/>
))
return (
<Row>
<Col>{inputFields}</Col>
<Col xs="auto">
<Button variant="danger" onClick={onRemove}>
Remove
</Button>
</Col>
</Row>
)
}
7 changes: 6 additions & 1 deletion src/fragmentarium/ui/fragment/CuneiformFragmentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ function EditorTab({
disabled: boolean
}): JSX.Element {
return (
<Tab eventKey={name} title={capitalize(name)} disabled={disabled}>
<Tab
key={name}
eventKey={name}
title={capitalize(name)}
disabled={disabled}
>
<ContentSection>{children}</ContentSection>
</Tab>
)
Expand Down
Loading

0 comments on commit 6ade26c

Please sign in to comment.