diff --git a/src/App.test.ts b/src/App.test.ts index aeefed8e9..c82e6b93d 100644 --- a/src/App.test.ts +++ b/src/App.test.ts @@ -1,6 +1,6 @@ import AppDriver from 'test-support/AppDriver' import FakeApi from 'test-support/FakeApi' -import { statisticsFactory } from 'test-support/fragment-fixtures' +import { statisticsFactory } from 'test-support/fragment-data-fixtures' import { tabIds as aboutTabIds } from 'about/ui/about' test.each([ diff --git a/src/chronology/ui/DateEditor/DateSelectionInput.test.tsx b/src/chronology/ui/DateEditor/DateSelectionInput.test.tsx index 97bc1141a..03ce73cc7 100644 --- a/src/chronology/ui/DateEditor/DateSelectionInput.test.tsx +++ b/src/chronology/ui/DateEditor/DateSelectionInput.test.tsx @@ -141,15 +141,15 @@ describe('Date Input Groups', () => { }) ) const yearInput = screen.getByLabelText('Year') - const yearBrokenSwitch = screen.getByLabelText('Year-Broken') - const yearUncertainSwitch = screen.getByLabelText('Year-Uncertain') + const yearBrokenSwitch = screen.getByTestId('year-broken-switch') + const yearUncertainSwitch = screen.getByTestId('year-uncertain-switch') const monthInput = screen.getByLabelText('Month') const monthIntercalaryCheckbox = screen.getByLabelText('Intercalary') - const monthBrokenSwitch = screen.getByLabelText('Month-Broken') - const monthUncertainSwitch = screen.getByLabelText('Month-Uncertain') + const monthBrokenSwitch = screen.getByTestId('month-broken-switch') + const monthUncertainSwitch = screen.getByTestId('month-uncertain-switch') const dayInput = screen.getByLabelText('Day') - const dayBrokenSwitch = screen.getByLabelText('Day-Broken') - const dayUncertainSwitch = screen.getByLabelText('Day-Uncertain') + const dayBrokenSwitch = screen.getByTestId('day-broken-switch') + const dayUncertainSwitch = screen.getByTestId('day-uncertain-switch') expect(yearInput).toBeInTheDocument() expect(yearBrokenSwitch).toBeInTheDocument() diff --git a/src/chronology/ui/DateEditor/DateSelectionInput.tsx b/src/chronology/ui/DateEditor/DateSelectionInput.tsx index 2923c66b8..a49c72969 100644 --- a/src/chronology/ui/DateEditor/DateSelectionInput.tsx +++ b/src/chronology/ui/DateEditor/DateSelectionInput.tsx @@ -13,8 +13,8 @@ import getDateConfigs from 'chronology/application/DateSelectionInputConfig' import { InputGroupProps, RadioButton, - getBrokenAndUncertainSwitches, } from 'chronology/ui/DateEditor/DateSelectionInputBase' +import { BrokenAndUncertainSwitches } from 'common/BrokenAndUncertain' type InputGroupsProps = { yearValue: string @@ -93,13 +93,15 @@ function getKingEponymSelect( ? EponymField({ ...props, assyrianPhase }) : KingField(props)} - {getBrokenAndUncertainSwitches({ - name, - isBroken: props[`${name}Broken`] ?? false, - isUncertain: props[`${name}Uncertain`] ?? false, - setBroken: props[`set${_.capitalize(name)}Broken`], - setUncertain: props[`set${_.capitalize(name)}Uncertain`], - })} +
@@ -195,13 +197,15 @@ function getDateInputGroup({ checked={isIntercalary} /> )} - {getBrokenAndUncertainSwitches({ - name, - isBroken, - isUncertain, - setBroken, - setUncertain, - })} + ) } diff --git a/src/chronology/ui/DateEditor/DateSelectionInputBase.tsx b/src/chronology/ui/DateEditor/DateSelectionInputBase.tsx index 147320804..53101d090 100644 --- a/src/chronology/ui/DateEditor/DateSelectionInputBase.tsx +++ b/src/chronology/ui/DateEditor/DateSelectionInputBase.tsx @@ -1,15 +1,7 @@ +import { BrokenUncertainProps } from 'common/BrokenAndUncertain' import React from 'react' -import _ from 'lodash' import { Form } from 'react-bootstrap' -export interface BrokenUncertainProps { - name: string - isBroken: boolean - isUncertain: boolean - setBroken: React.Dispatch> - setUncertain: React.Dispatch> -} - export interface InputGroupProps extends BrokenUncertainProps { value: string isIntercalary?: boolean @@ -42,30 +34,3 @@ export const RadioButton = ({ onChange={onChange} /> ) - -export function getBrokenAndUncertainSwitches({ - name, - isBroken, - isUncertain, - setBroken, - setUncertain, -}: BrokenUncertainProps): JSX.Element { - return ( - <> - setBroken(event.target.checked)} - checked={isBroken} - /> - setUncertain(event.target.checked)} - checked={isUncertain} - /> - - ) -} diff --git a/src/common/BrokenAndUncertain.tsx b/src/common/BrokenAndUncertain.tsx new file mode 100644 index 000000000..87d404356 --- /dev/null +++ b/src/common/BrokenAndUncertain.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { Form } from 'react-bootstrap' + +export interface BrokenUncertainProps { + name: string + isBroken?: boolean + isUncertain?: boolean + setBroken: + | React.Dispatch> + | ((isBroken: boolean) => void) + setUncertain: + | React.Dispatch> + | ((isUncertain: boolean) => void) +} + +export function BrokenAndUncertainSwitches({ + name, + isBroken = false, + isUncertain = false, + setBroken, + setUncertain, +}: BrokenUncertainProps): JSX.Element { + return ( + <> + setBroken(event.target.checked)} + checked={isBroken} + /> + setUncertain(event.target.checked)} + checked={isUncertain} + /> + + ) +} diff --git a/src/fragmentarium/application/FindspotService.test.ts b/src/fragmentarium/application/FindspotService.test.ts index 18704766c..fecb7673b 100644 --- a/src/fragmentarium/application/FindspotService.test.ts +++ b/src/fragmentarium/application/FindspotService.test.ts @@ -1,4 +1,4 @@ -import { findspotFactory } from 'test-support/fragment-fixtures' +import { findspotFactory } from 'test-support/fragment-data-fixtures' import { FindspotService } from './FindspotService' import { testDelegation, TestData } from 'test-support/utils' diff --git a/src/fragmentarium/application/FragmentService.test.ts b/src/fragmentarium/application/FragmentService.test.ts index fd5cb4ae3..32ad19813 100644 --- a/src/fragmentarium/application/FragmentService.test.ts +++ b/src/fragmentarium/application/FragmentService.test.ts @@ -17,10 +17,10 @@ import LemmatizationFactory from './LemmatizationFactory' import BibliographyService from 'bibliography/application/BibliographyService' import WordRepository from 'dictionary/infrastructure/WordRepository' import { - archaeologyFactory, fragmentFactory, manuscriptAttestationFactory, } from 'test-support/fragment-fixtures' +import { archaeologyFactory } from 'test-support/fragment-data-fixtures' import { bibliographyEntryFactory, referenceFactory, @@ -58,15 +58,17 @@ const fragmentRepository = { updateIntroduction: jest.fn(), updateNotes: jest.fn(), updateLemmatization: jest.fn(), - fetchGenres: jest.fn(), - fetchProvenances: jest.fn(), updateGenres: jest.fn(), updateScript: jest.fn(), updateDate: jest.fn(), updateDatesInText: jest.fn(), + fetchGenres: jest.fn(), + fetchProvenances: jest.fn(), fetchPeriods: jest.fn(), + fetchColophonNames: jest.fn(), updateReferences: jest.fn(), updateArchaeology: jest.fn(), + updateColophon: jest.fn(), folioPager: jest.fn(), fragmentPager: jest.fn(), findLemmas: jest.fn(), @@ -199,6 +201,7 @@ describe('methods returning fragment', () => { let fragment: Fragment let result: Fragment let genreResult: string[][] + let colophonNamesResult: string[] const genreOptions = [['ARCHIVE', 'Administrative']] const genres: Genres = Genres.fromJson([ { @@ -206,6 +209,7 @@ describe('methods returning fragment', () => { uncertain: false, }, ]) + const colophonNamesOptions = [['Humbaba', 'Enkidu']] const date: MesopotamianDate = MesopotamianDate.fromJson({ year: { value: '1' }, month: { value: '1' }, @@ -373,6 +377,19 @@ describe('methods returning fragment', () => { expect(fragmentRepository.fetchGenres).toHaveBeenCalled()) }) + describe('fetch colophon names', () => { + beforeEach(async () => { + fragmentRepository.fetchColophonNames.mockReturnValue( + Promise.resolve(colophonNamesOptions) + ) + colophonNamesResult = await fragmentService.fetchColophonNames('u') + }) + test('returns names', () => + expect(colophonNamesResult).toEqual(colophonNamesOptions)) + test('calls repository with correct parameters', () => + expect(fragmentRepository.fetchColophonNames).toHaveBeenCalled()) + }) + describe('update genre', () => { let expectedFragment: Fragment diff --git a/src/fragmentarium/application/FragmentService.ts b/src/fragmentarium/application/FragmentService.ts index 817e076eb..6db58245e 100644 --- a/src/fragmentarium/application/FragmentService.ts +++ b/src/fragmentarium/application/FragmentService.ts @@ -26,6 +26,7 @@ import { FragmentQuery } from 'query/FragmentQuery' import { MesopotamianDate } from 'chronology/domain/Date' import { FragmentAfoRegisterQueryResult, QueryResult } from 'query/QueryResult' import { ArchaeologyDto } from 'fragmentarium/domain/archaeologyDtos' +import { Colophon } from 'fragmentarium/domain/Colophon' export type ThumbnailSize = 'small' | 'medium' | 'large' @@ -65,6 +66,7 @@ export interface FragmentRepository { fetchGenres(): Bluebird fetchProvenances(): Bluebird fetchPeriods(): Bluebird + fetchColophonNames(query: string): Bluebird updateGenres(number: string, genres: Genres): Bluebird updateScript(number: string, script: Script): Bluebird updateDate(number: string, date: MesopotamianDate): Bluebird @@ -90,6 +92,7 @@ export interface FragmentRepository { number: string, archaeology: ArchaeologyDto ): Bluebird + updateColophon(number: string, colophon: Colophon): Bluebird folioPager(folio: Folio, fragmentNumber: string): Bluebird fragmentPager(fragmentNumber: string): Bluebird findLemmas(lemma: string, isNormalized: boolean): Bluebird @@ -184,6 +187,7 @@ export class FragmentService { fetchGenres(): Bluebird { return this.fragmentRepository.fetchGenres() } + fetchProvenances(): Bluebird { return this.fragmentRepository.fetchProvenances() } @@ -192,6 +196,10 @@ export class FragmentService { return this.fragmentRepository.fetchPeriods() } + fetchColophonNames(query: string): Bluebird { + return this.fragmentRepository.fetchColophonNames(query) + } + listAllFragments(): Bluebird { return this.fragmentRepository.listAllFragments() } @@ -262,6 +270,12 @@ export class FragmentService { .then((fragment: Fragment) => this.injectReferences(fragment)) } + updateColophon(number: string, colophon: Colophon): Bluebird { + return this.fragmentRepository + .updateColophon(number, colophon) + .then((fragment: Fragment) => this.injectReferences(fragment)) + } + findInCorpus(number: string): Bluebird> { return this.fragmentRepository.findInCorpus(number) } diff --git a/src/fragmentarium/domain/Colophon.test.ts b/src/fragmentarium/domain/Colophon.test.ts new file mode 100644 index 000000000..7629465a8 --- /dev/null +++ b/src/fragmentarium/domain/Colophon.test.ts @@ -0,0 +1,113 @@ +import { + Colophon, + IndividualAttestation, + ColophonStatus, + ColophonOwnership, + ColophonType, + IndividualType, +} from 'fragmentarium/domain/Colophon' + +describe('Colophon', () => { + describe('constructor', () => { + it('should initialize with empty values if no arguments provided', () => { + const colophon = new Colophon({}) + expect(colophon.colophonStatus).toBeUndefined() + expect(colophon.colophonOwnership).toBeUndefined() + expect(colophon.colophonTypes).toBeUndefined() + expect(colophon.originalFrom).toBeUndefined() + expect(colophon.writtenIn).toBeUndefined() + expect(colophon.notesToScribalProcess).toBeUndefined() + expect(colophon.individuals).toBeUndefined() + }) + + it('should properly assign values', () => { + const colophon = new Colophon({ + colophonStatus: ColophonStatus.Yes, + colophonOwnership: ColophonOwnership.Library, + colophonTypes: [ColophonType.AsbA], + originalFrom: { value: 'Babylon', isBroken: false }, + writtenIn: { value: 'Assyria', isUncertain: true }, + notesToScribalProcess: 'Detailed notes', + individuals: [ + new IndividualAttestation({ name: { value: 'John Doe' } }), + ], + }) + + expect(colophon.colophonStatus).toEqual(ColophonStatus.Yes) + expect(colophon.colophonOwnership).toEqual(ColophonOwnership.Library) + expect(colophon.colophonTypes).toEqual([ColophonType.AsbA]) + expect(colophon.originalFrom?.value).toEqual('Babylon') + expect(colophon.writtenIn?.isUncertain).toBeTruthy() + expect(colophon.notesToScribalProcess).toEqual('Detailed notes') + expect(colophon.individuals?.length).toEqual(1) + if (colophon.individuals) { + expect(colophon.individuals[0]?.name?.value).toEqual('John Doe') + } + }) + }) + + describe('fromJson', () => { + it('should create an instance from a DTO', () => { + const colophonDto = { + colophonStatus: ColophonStatus.Yes, + colophonOwnership: ColophonOwnership.Private, + colophonTypes: [ColophonType.AsbE], + originalFrom: { value: 'Babylon', isBroken: false }, + writtenIn: { value: 'Assur', isUncertain: true }, + notesToScribalProcess: 'Notes here', + individuals: [{ name: { value: 'Jane Doe', isUncertain: true } }], + } + + const colophon = Colophon.fromJson(colophonDto) + + expect(colophon.colophonStatus).toEqual(ColophonStatus.Yes) + expect(colophon.colophonOwnership).toEqual(ColophonOwnership.Private) + expect(colophon.colophonTypes).toEqual([ColophonType.AsbE]) + expect(colophon.originalFrom?.value).toEqual('Babylon') + expect(colophon.writtenIn?.value).toEqual('Assur') + expect(colophon.notesToScribalProcess).toEqual('Notes here') + expect(colophon.individuals?.length).toEqual(1) + if (colophon.individuals) { + expect(colophon.individuals[0].name?.value).toEqual('Jane Doe') + } + }) + }) +}) + +describe('IndividualAttestation', () => { + it('should correctly initialize properties', () => { + const attestation = new IndividualAttestation({ + name: { value: 'John Doe', isBroken: true }, + type: { value: IndividualType.Owner }, + }) + + expect(attestation.name?.value).toEqual('John Doe') + expect(attestation.name?.isBroken).toBeTruthy() + expect(attestation.type?.value).toEqual(IndividualType.Owner) + }) + + it('should update name field properly', () => { + let attestation = new IndividualAttestation({ + name: { value: 'Initial Name' }, + }) + attestation = attestation.setNameField('name', { value: 'Updated Name' }) + expect(attestation.name?.value).toEqual('Updated Name') + }) + + it('should update type field properly', () => { + let individual = new IndividualAttestation({ + type: { value: IndividualType.Scribe }, + }) + individual = individual.setTypeField({ value: IndividualType.Owner }) + expect(individual.type?.value).toEqual(IndividualType.Owner) + }) + + it('should format toString output correctly', () => { + const attestation = new IndividualAttestation({ + type: { value: IndividualType.Scribe }, + name: { value: 'John Doe' }, + sonOf: { value: 'John Senior' }, + }) + expect(attestation.toString()).toEqual('Scribe: John Doe, s. John Senior') + }) +}) diff --git a/src/fragmentarium/domain/Colophon.ts b/src/fragmentarium/domain/Colophon.ts new file mode 100644 index 000000000..a15a2651e --- /dev/null +++ b/src/fragmentarium/domain/Colophon.ts @@ -0,0 +1,220 @@ +import produce, { Draft, castDraft, immerable } from 'immer' + +export enum ColophonStatus { + Yes = 'Yes', + No = 'No', + Broken = 'Broken', + OnlyColophon = 'Only Colophon', +} + +export enum ColophonType { + AsbA = 'Asb a', + AsbB = 'Asb b', + AsbC = 'Asb c', + AsbD = 'Asb d', + AsbE = 'Asb e', + AsbF = 'Asb f', + AsbG = 'Asb g BAK 321', + AsbH = 'Asb h', + AsbI = 'Asb i', + AsbK = 'Asb k', + AsbL = 'Asb l', + AsbM = 'Asb m', + AsbN = 'Asb n', + AsbO = 'Asb o', + AsbP = 'Asb p', + AsbQ = 'Asb q', + AsbRS = 'Asb r/s', + AsbT = 'Asb t', + AsbU = 'Asb u', + AsbV = 'Asb v', + AsbW = 'Asb w', + AsbUnclear = 'Asb Unclear', + NzkBAK293 = 'Nzk BAK 293', + NzkBAK294 = 'Nzk BAK 294', + NzkBAK295 = 'Nzk BAK 295', + NzkBAK296 = 'Nzk BAK 296', + NzkBAK297 = 'Nzk BAK 297', +} + +export enum ColophonOwnership { + Library = 'Library', + Private = 'Private', + Individual = 'Individual', +} + +export enum IndividualType { + Owner = 'Owner', + Scribe = 'Scribe', + Other = 'Other', +} + +export interface NameAttestation { + readonly value?: string + readonly isBroken?: boolean + readonly isUncertain?: boolean +} + +export interface ProvenanceAttestation { + readonly value?: string + readonly isBroken?: boolean + readonly isUncertain?: boolean +} + +export interface IndividualTypeAttestation { + readonly value?: IndividualType + readonly isBroken?: boolean + readonly isUncertain?: boolean +} + +export interface IndividualAttestationDto { + readonly name?: NameAttestation + readonly sonOf?: NameAttestation + readonly grandsonOf?: NameAttestation + readonly family?: NameAttestation + readonly nativeOf?: ProvenanceAttestation + readonly type?: IndividualTypeAttestation +} + +export class IndividualAttestation { + readonly [immerable] = true + readonly name?: NameAttestation + readonly sonOf?: NameAttestation + readonly grandsonOf?: NameAttestation + readonly family?: NameAttestation + readonly nativeOf?: ProvenanceAttestation + readonly type?: IndividualTypeAttestation + + constructor({ + name, + sonOf, + grandsonOf, + family, + nativeOf, + type, + }: { + readonly name?: NameAttestation + readonly sonOf?: NameAttestation + readonly grandsonOf?: NameAttestation + readonly family?: NameAttestation + readonly nativeOf?: ProvenanceAttestation + readonly type?: IndividualTypeAttestation + }) { + this.name = name + this.sonOf = sonOf + this.grandsonOf = grandsonOf + this.family = family + this.nativeOf = nativeOf + this.type = type + } + + setNameField( + field: 'name' | 'sonOf' | 'grandsonOf' | 'family', + name?: NameAttestation + ): IndividualAttestation { + return produce(this, (draft: Draft) => { + draft[field] = castDraft(name) + }) + } + + setTypeField(type?: IndividualTypeAttestation): IndividualAttestation { + return produce(this, (draft: Draft) => { + draft.type = castDraft(type) + }) + } + + setNativeOf(provenance?: ProvenanceAttestation): IndividualAttestation { + return produce(this, (draft: Draft) => { + draft.nativeOf = castDraft(provenance) + }) + } + + toString(): string { + return `${this.typeString}${[ + this.nameString, + this.sonOfString, + this.grandsonOfString, + this.familyString, + this.nativeOfString, + ] + .filter((value) => value !== '') + .join(', ')}` + } + + private get typeString(): string { + return this?.type?.value ? `${this.type.value}: ` : '' + } + private get nameString(): string { + return this?.name?.value ?? '' + } + + private get sonOfString(): string { + return this?.sonOf?.value ? `s. ${this.sonOf.value}` : '' + } + private get grandsonOfString(): string { + return this?.grandsonOf?.value ? `gs. ${this.grandsonOf.value}` : '' + } + + private get familyString(): string { + return this?.family?.value ? `f. ${this.family.value}` : '' + } + + private get nativeOfString(): string { + return this?.nativeOf?.value ? `n. ${this.nativeOf.value}` : '' + } +} + +export interface ColophonDto { + readonly colophonStatus?: ColophonStatus + readonly colophonOwnership?: ColophonOwnership + readonly colophonTypes?: ColophonType[] + readonly originalFrom?: ProvenanceAttestation + readonly writtenIn?: ProvenanceAttestation + readonly notesToScribalProcess?: string + readonly individuals?: IndividualAttestationDto[] +} + +export class Colophon { + readonly colophonStatus?: ColophonStatus + readonly colophonOwnership?: ColophonOwnership + readonly colophonTypes?: ColophonType[] + readonly originalFrom?: ProvenanceAttestation + readonly writtenIn?: ProvenanceAttestation + readonly notesToScribalProcess?: string + readonly individuals?: IndividualAttestation[] + + constructor({ + colophonStatus, + colophonOwnership, + colophonTypes, + originalFrom, + writtenIn, + notesToScribalProcess, + individuals, + }: { + readonly colophonStatus?: ColophonStatus + readonly colophonOwnership?: ColophonOwnership + readonly colophonTypes?: ColophonType[] + readonly originalFrom?: ProvenanceAttestation + readonly writtenIn?: ProvenanceAttestation + readonly notesToScribalProcess?: string + readonly individuals?: IndividualAttestation[] + }) { + this.colophonStatus = colophonStatus + this.colophonOwnership = colophonOwnership + this.colophonTypes = colophonTypes + this.originalFrom = originalFrom + this.writtenIn = writtenIn + this.notesToScribalProcess = notesToScribalProcess + this.individuals = individuals + } + + static fromJson(colophonDto: ColophonDto): Colophon { + return new Colophon({ + ...colophonDto, + individuals: colophonDto?.individuals?.map( + (indiviual) => new IndividualAttestation(indiviual) + ), + }) + } +} diff --git a/src/fragmentarium/domain/FragmentDtos.ts b/src/fragmentarium/domain/FragmentDtos.ts index 020c66d60..453a6547b 100644 --- a/src/fragmentarium/domain/FragmentDtos.ts +++ b/src/fragmentarium/domain/FragmentDtos.ts @@ -10,6 +10,7 @@ import { } from 'chronology/domain/DateParameters' import { ArchaeologyDto } from './archaeologyDtos' import { MuseumKey } from './museum' +import { ColophonDto } from 'fragmentarium/domain/Colophon' interface MeasureDto { value?: number @@ -118,4 +119,5 @@ export default interface FragmentDto { archaeology?: Omit & { excavationNumber?: MuseumNumber } + colophon?: ColophonDto } diff --git a/src/fragmentarium/domain/archaeology.test.ts b/src/fragmentarium/domain/archaeology.test.ts index 457cd0057..17e8cccbd 100644 --- a/src/fragmentarium/domain/archaeology.test.ts +++ b/src/fragmentarium/domain/archaeology.test.ts @@ -3,7 +3,7 @@ import { archaeologyFactory, dateRangeFactory, findspotFactory, -} from 'test-support/fragment-fixtures' +} from 'test-support/fragment-data-fixtures' import { BuildingType, Findspot, diff --git a/src/fragmentarium/domain/fragment.ts b/src/fragmentarium/domain/fragment.ts index 0fbb8bfd1..10b949435 100644 --- a/src/fragmentarium/domain/fragment.ts +++ b/src/fragmentarium/domain/fragment.ts @@ -19,6 +19,7 @@ import { RecordEntry } from './RecordEntry' import { ResearchProject } from 'research-projects/researchProject' import { MesopotamianDate } from 'chronology/domain/Date' import { Archaeology } from './archaeology' +import { Colophon } from 'fragmentarium/domain/Colophon' export interface FragmentInfo { readonly number: string @@ -76,6 +77,36 @@ export interface ScriptDto { readonly uncertain: boolean } +interface FragmentProps { + number: string + accession: string + publication: string + joins: Joins + description: string + measures: Measures + collection: string + legacyScript: string + folios: ReadonlyArray + record: ReadonlyArray + text: Text + notes: Notes + museum: Museum + references: ReadonlyArray + uncuratedReferences?: ReadonlyArray | null + traditionalReferences: readonly string[] + atf: string + hasPhoto: boolean + genres: Genres + introduction: Introduction + script: Script + externalNumbers: ExternalNumbers + projects: ReadonlyArray + date?: MesopotamianDate + datesInText?: ReadonlyArray + archaeology?: Archaeology + colophon?: Colophon +} + export class Fragment { readonly [immerable] = true @@ -105,91 +136,39 @@ export class Fragment { readonly projects: ReadonlyArray, readonly date?: MesopotamianDate, readonly datesInText?: ReadonlyArray, - readonly archaeology?: Archaeology + readonly archaeology?: Archaeology, + readonly colophon?: Colophon ) {} - static create({ - number, - accession, - publication, - joins, - description, - measures, - collection, - legacyScript, - folios, - record, - text, - notes, - museum, - references, - uncuratedReferences, - traditionalReferences, - atf, - hasPhoto, - genres, - introduction, - script, - externalNumbers, - projects, - date, - datesInText, - archaeology, - }: { - number: string - accession: string - publication: string - joins: Joins - description: string - measures: Measures - collection: string - legacyScript: string - folios: ReadonlyArray - record: ReadonlyArray - text: Text - notes: Notes - museum: Museum - references: ReadonlyArray - uncuratedReferences?: ReadonlyArray | null - traditionalReferences: readonly string[] - atf: string - hasPhoto: boolean - genres: Genres - introduction: Introduction - script: Script - externalNumbers: ExternalNumbers - projects: ReadonlyArray - date?: MesopotamianDate - datesInText?: ReadonlyArray - archaeology?: Archaeology - }): Fragment { + static create(props: FragmentProps): Fragment { return new Fragment( - number, - accession, - publication, - joins, - description, - measures, - collection, - legacyScript, - folios, - record, - text, - notes, - museum, - references, - uncuratedReferences ?? null, - traditionalReferences, - atf, - hasPhoto, - genres, - introduction, - script, - externalNumbers, - projects, - date, - datesInText, - archaeology + props.number, + props.accession, + props.publication, + props.joins, + props.description, + props.measures, + props.collection, + props.legacyScript, + props.folios, + props.record, + props.text, + props.notes, + props.museum, + props.references, + props?.uncuratedReferences ?? null, + props.traditionalReferences, + props.atf, + props.hasPhoto, + props.genres, + props.introduction, + props.script, + props.externalNumbers, + props.projects, + props.date, + props.datesInText, + props.archaeology, + props.colophon ) } diff --git a/src/fragmentarium/infrastructure/FindspotRepository.test.ts b/src/fragmentarium/infrastructure/FindspotRepository.test.ts index 374905926..edd524cd3 100644 --- a/src/fragmentarium/infrastructure/FindspotRepository.test.ts +++ b/src/fragmentarium/infrastructure/FindspotRepository.test.ts @@ -1,4 +1,4 @@ -import { findspotFactory } from 'test-support/fragment-fixtures' +import { findspotFactory } from 'test-support/fragment-data-fixtures' import { ApiFindspotRepository } from './FindspotRepository' import { testDelegation, TestData } from 'test-support/utils' import { toFindspotDto } from 'fragmentarium/domain/archaeologyDtos' diff --git a/src/fragmentarium/infrastructure/FragmentRepository.test.ts b/src/fragmentarium/infrastructure/FragmentRepository.test.ts index 8619d3aa9..482f8e1d0 100644 --- a/src/fragmentarium/infrastructure/FragmentRepository.test.ts +++ b/src/fragmentarium/infrastructure/FragmentRepository.test.ts @@ -12,7 +12,7 @@ import { queryItemFactory } from 'test-support/query-item-factory' import { museumNumberToString } from 'fragmentarium/domain/MuseumNumber' import { Genre, Genres } from 'fragmentarium/domain/Genres' import { mesopotamianDateFactory } from 'test-support/date-fixtures' -import { archaeologyFactory } from 'test-support/fragment-fixtures' +import { archaeologyFactory } from 'test-support/fragment-data-fixtures' import { FragmentInfo, FragmentInfoDto } from 'fragmentarium/domain/fragment' const apiClient = { diff --git a/src/fragmentarium/infrastructure/FragmentRepository.ts b/src/fragmentarium/infrastructure/FragmentRepository.ts index 28b3069b1..93efd2bb2 100644 --- a/src/fragmentarium/infrastructure/FragmentRepository.ts +++ b/src/fragmentarium/infrastructure/FragmentRepository.ts @@ -52,6 +52,7 @@ import { MesopotamianDate } from 'chronology/domain/Date' import { ArchaeologyDto } from 'fragmentarium/domain/archaeologyDtos' import { createArchaeology } from 'fragmentarium/domain/archaeologyDtos' import { JsonApiClient } from 'index' +import { Colophon } from 'fragmentarium/domain/Colophon' export function createScript(dto: ScriptDto): Script { return { @@ -110,6 +111,7 @@ function createFragment(dto: FragmentDto): Fragment { archaeology: dto.archaeology ? createArchaeology(dto.archaeology) : undefined, + colophon: dto.colophon ? Colophon.fromJson(dto.colophon) : undefined, }) } @@ -197,10 +199,18 @@ class ApiFragmentRepository fetchGenres(): Promise { return this.apiClient.fetchJson('/genres', false) } + fetchProvenances(): Promise { return this.apiClient.fetchJson('/provenances', false) } + fetchColophonNames(query: string): Promise { + return this.apiClient.fetchJson( + `/fragments/colophon-names?${stringify({ query })}`, + false + ) + } + fetchPeriods(): Promise { return this.apiClient.fetchJson('/periods', false) } @@ -297,6 +307,13 @@ class ApiFragmentRepository .then(createFragment) } + updateColophon(number: string, colophon: Colophon): Promise { + const path = createFragmentPath(number, 'colophon') + return this.apiClient + .postJson(path, { colophon: colophon }) + .then(createFragment) + } + folioPager(folio: Folio, number: string): Promise { return this.apiClient.fetchJson( `/fragments/${encodeURIComponent(number)}/pager/${encodeURIComponent( diff --git a/src/fragmentarium/infrastructure/ImageRepository.test.ts b/src/fragmentarium/infrastructure/ImageRepository.test.ts index 28e63a946..5fd10eb7d 100644 --- a/src/fragmentarium/infrastructure/ImageRepository.test.ts +++ b/src/fragmentarium/infrastructure/ImageRepository.test.ts @@ -1,7 +1,7 @@ import Promise from 'bluebird' import ApiImageRepository from './ImageRepository' import Folio from 'fragmentarium/domain/Folio' -import { folioFactory } from 'test-support/fragment-fixtures' +import { folioFactory } from 'test-support/fragment-data-fixtures' import { ThumbnailSize } from 'fragmentarium/application/FragmentService' import { ApiError } from 'http/ApiClient' diff --git a/src/fragmentarium/ui/ProvenanceSearchForm.tsx b/src/fragmentarium/ui/ProvenanceSearchForm.tsx index 80ab2b3ad..df9494957 100644 --- a/src/fragmentarium/ui/ProvenanceSearchForm.tsx +++ b/src/fragmentarium/ui/ProvenanceSearchForm.tsx @@ -8,11 +8,12 @@ export default withData< { onChange: (value: string | null) => void value?: string | null + placeholder?: string }, { fragmentService: FragmentService }, ReadonlyArray> >( - ({ data, value, onChange }) => { + ({ data, value, placeholder, onChange }) => { const options = data.map((site) => ({ value: site.join(' '), label: site.join(' '), @@ -22,7 +23,7 @@ export default withData< return ( { + const _individual = individual.setTypeField({ + ...individual.type, + value: option?.value ? IndividualType[option?.value] : undefined, + }) + props.onChange(_individual, index) + }, + }} + /> +) diff --git a/src/fragmentarium/ui/fragment/ColophonEditorInputs.tsx b/src/fragmentarium/ui/fragment/ColophonEditorInputs.tsx new file mode 100644 index 000000000..08a301852 --- /dev/null +++ b/src/fragmentarium/ui/fragment/ColophonEditorInputs.tsx @@ -0,0 +1,176 @@ +import React from 'react' +import { Form, Col, Row } from 'react-bootstrap' +import Select from 'react-select' +import { + Colophon, + ColophonOwnership, + ColophonStatus, + ColophonType, +} from 'fragmentarium/domain/Colophon' +import _ from 'lodash' +import ProvenanceSearchForm from '../ProvenanceSearchForm' +import FragmentService from 'fragmentarium/application/FragmentService' +import { BrokenAndUncertainSwitches } from 'common/BrokenAndUncertain' + +export const ProvenanceAttestationInput = ({ + fieldName, + onChange, + fragmentService, + colophon, +}: { + fieldName: 'originalFrom' | 'writtenIn' + onChange: (field: keyof Colophon, value) => void + fragmentService: FragmentService + colophon: Colophon +}): JSX.Element => { + const provenanceAttestation = colophon[fieldName] + return ( + + {_.startCase(fieldName)} + { + onChange(fieldName, { + ...provenanceAttestation, + value: value ?? null, + }) + }} + value={provenanceAttestation?.value ?? null} + placeholder={_.startCase(fieldName)} + /> + + { + onChange(fieldName, { ...provenanceAttestation, isBroken }) + }, + setUncertain: (isUncertain: boolean) => { + onChange(fieldName, { ...provenanceAttestation, isUncertain }) + }, + }} + /> + + + ) +} + +export const ColophonStatusInput = ({ + colophonStatus, + onChange, +}: { + colophonStatus?: ColophonStatus + onChange: (field: keyof Colophon, value) => void +}): JSX.Element => { + const options = Object.values(ColophonStatus).map((status) => ({ + value: status, + label: status, + })) + return ( + + Colophon Status + onChange('colophonOwnership', option?.value)} + isClearable={true} + placeholder="Ownership" + /> + + ) +} + +export const ColophonTypeInput = ({ + colophonTypes, + onChange, +}: { + colophonTypes?: ColophonType[] + onChange: (field: keyof Colophon, value) => void +}): JSX.Element => { + const options = Object.values(ColophonType).map((type) => ({ + value: type, + label: type, + })) + const colophonTypeValues = colophonTypes?.map((type) => ({ + value: type, + label: type, + })) + return ( + + Colophon Type +