From 0f9030dd7439d3e3409052084fc18380b585843e Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 6 Feb 2024 16:23:53 +0000 Subject: [PATCH] Implement kings as service (WiP) --- src/chronology/application/DateSelection.tsx | 7 + .../application/DateSelectionMethods.ts | 35 +++-- .../application/DateSelectionState.ts | 24 ++- src/chronology/application/KingsService.ts | 58 +++++++ src/chronology/domain/Date.ts | 9 +- src/chronology/domain/DateBase.ts | 8 +- src/chronology/domain/DateConverter.ts | 8 +- .../infrastructure/KingsRepository.ts | 15 ++ .../ui/BrinkmanKings/BrinkmanKings.test.tsx | 14 ++ .../ui/BrinkmanKings/BrinkmanKings.tsx | 143 ++++++++++-------- .../ui/BrinkmanKings/BrinkmanKingsTable.tsx | 43 +++++- .../ui/DateConverter/DateConverterForm.tsx | 17 ++- .../ui/DateEditor/DateSelectionInput.tsx | 2 + .../ui/DateEditor/DatesInTextSelection.tsx | 17 ++- src/chronology/ui/DateEditor/Eponyms.tsx | 9 +- .../infrastructure/FragmentRepository.ts | 48 ++++-- .../ui/fragment/CuneiformFragment.tsx | 7 + .../ui/fragment/FragmentView.tsx | 4 + src/fragmentarium/ui/info/Details.tsx | 10 +- src/fragmentarium/ui/info/Info.tsx | 4 + src/index.tsx | 140 +++++++++++++---- src/router/fragmentariumRoutes.tsx | 4 + src/router/router.tsx | 2 + src/router/toolsRoutes.tsx | 10 +- src/signs/infrastructure/SignRepository.ts | 8 +- src/test-support/AppDriver.tsx | 9 +- 26 files changed, 502 insertions(+), 153 deletions(-) create mode 100644 src/chronology/application/KingsService.ts create mode 100644 src/chronology/infrastructure/KingsRepository.ts diff --git a/src/chronology/application/DateSelection.tsx b/src/chronology/application/DateSelection.tsx index 13cff382c..2a8a3399f 100644 --- a/src/chronology/application/DateSelection.tsx +++ b/src/chronology/application/DateSelection.tsx @@ -15,6 +15,7 @@ import { import useDateSelectionState, { DateEditorStateProps, } from 'chronology/application/DateSelectionState' +import KingsService from './KingsService' type Props = { dateProp?: MesopotamianDate @@ -22,12 +23,14 @@ type Props = { inList?: boolean index?: number saveDateOverride?: (updatedDate?: MesopotamianDate, index?: number) => void + kingsService: KingsService } interface DateEditorProps extends DateEditorStateProps { target: React.MutableRefObject isSaving: boolean isDisplayed: boolean + kingsService: KingsService } export function DateEditor({ @@ -41,6 +44,7 @@ export function DateEditor({ setIsDisplayed, setIsSaving, saveDateOverride, + kingsService, }: DateEditorProps): JSX.Element { const state = useDateSelectionState({ date, @@ -50,6 +54,7 @@ export function DateEditor({ setIsDisplayed, setIsSaving, saveDateOverride, + kingsService, }) const dateOptionsInput = DateOptionsInput({ ...state }) @@ -114,6 +119,7 @@ export default function DateSelection({ inList = false, index, saveDateOverride, + kingsService, }: Props): JSX.Element { const target = useRef(null) const [isDisplayed, setIsDisplayed] = useState(false) @@ -149,6 +155,7 @@ export default function DateSelection({ setDate={setDate} index={index} saveDateOverride={saveDateOverride} + kingsService={kingsService} /> ) diff --git a/src/chronology/application/DateSelectionMethods.ts b/src/chronology/application/DateSelectionMethods.ts index 2dc218d47..f680b42ca 100644 --- a/src/chronology/application/DateSelectionMethods.ts +++ b/src/chronology/application/DateSelectionMethods.ts @@ -4,6 +4,7 @@ import { DateFieldDto, MonthFieldDto } from 'fragmentarium/domain/FragmentDtos' import { Fragment } from 'fragmentarium/domain/fragment' import { DateSelectionStateParams } from './DateSelectionState' import { EponymDateField, KingDateField } from 'chronology/domain/DateBase' +import KingsService from './KingsService' interface SaveDateParams { date?: MesopotamianDate @@ -45,20 +46,26 @@ export function saveDateDefault({ } } -export function getDate(params: DateSelectionStateParams): MesopotamianDate { - return MesopotamianDate.fromJson({ - year: getYear(params), - month: getMonth(params), - day: getDay(params), - king: getKing(params), - eponym: getEponym(params), - isSeleucidEra: params.isSeleucidEra, - isAssyrianDate: params.isAssyrianDate, - ur3Calendar: - params.ur3Calendar && params.isCalendarFieldDisplayed - ? params.ur3Calendar - : undefined, - }) +export function getDate( + params: DateSelectionStateParams, + kingsService: KingsService +): MesopotamianDate { + return MesopotamianDate.fromJson( + { + year: getYear(params), + month: getMonth(params), + day: getDay(params), + king: getKing(params), + eponym: getEponym(params), + isSeleucidEra: params.isSeleucidEra, + isAssyrianDate: params.isAssyrianDate, + ur3Calendar: + params.ur3Calendar && params.isCalendarFieldDisplayed + ? params.ur3Calendar + : undefined, + }, + kingsService + ) } function getYear(params: DateSelectionStateParams): DateFieldDto { diff --git a/src/chronology/application/DateSelectionState.ts b/src/chronology/application/DateSelectionState.ts index 22d00a17f..8a75d93a8 100644 --- a/src/chronology/application/DateSelectionState.ts +++ b/src/chronology/application/DateSelectionState.ts @@ -2,7 +2,6 @@ import { MesopotamianDate } from 'chronology/domain/Date' import { EponymDateField, Ur3Calendar } from 'chronology/domain/DateBase' import { KingDateField } from 'chronology/domain/DateBase' import usePromiseEffect from 'common/usePromiseEffect' -import { Eponym } from 'chronology/ui/DateEditor/Eponyms' import { useState } from 'react' import Bluebird from 'bluebird' import { Fragment } from 'fragmentarium/domain/fragment' @@ -10,6 +9,19 @@ import { getDate, saveDateDefault, } from 'chronology/application/DateSelectionMethods' +import KingsService from './KingsService' + +// ToDo: +// - Test `isNotInBrinkman` +// - Exclude empty dates and make it impossible to save them +// - If year, month, and day are empty and not broken or undertain, do not display ∅.∅.∅, instead display only the name of the king. +// - If day, month or year are not given, do not calculate the exact date, instead only the year. +// - Accession year: year 0 of the king belongs with the last year of the prev. king +// - Address date converter issues: +// 1. January & February (both Gregorian & Julian) drifts upon change, +// 2. Check dates around 1 BCE / CE, +// 3. Fix errors with first and last ruler +// - Instead of PGC, give the Julian date by default and show an info icon. When hovering over the icon, the user will get the PGC date. export interface DateEditorStateProps { date?: MesopotamianDate @@ -19,6 +31,7 @@ export interface DateEditorStateProps { setIsSaving: React.Dispatch> index?: number saveDateOverride?: (updatedDate?: MesopotamianDate, index?: number) => void + kingsService: KingsService } interface YearStateParams { @@ -74,7 +87,7 @@ interface KingAndEponymDateParams extends KingAndEponymBrokenUncertainParams { king?: KingDateField eponym?: EponymDateField setKing: React.Dispatch> - setEponym: React.Dispatch> + setEponym: React.Dispatch> } interface AdditionalDateStateParams @@ -88,7 +101,9 @@ export interface DateSelectionStateParams extends YearStateParams, MonthStateParams, DayStateParams, - AdditionalDateStateParams {} + AdditionalDateStateParams { + kingsService: KingsService +} export interface DateSelectionState extends DateSelectionStateParams { getDate: () => MesopotamianDate @@ -241,6 +256,7 @@ export default function useDateSelectionState( ...useMonthState(date), ...useDayState(date), ...useAdditionalDateParams(date), + kingsService: props.kingsService, } const _saveDate = (updatedDate) => saveDateDefault({ @@ -250,7 +266,7 @@ export default function useDateSelectionState( updatedDate, }) - const _getDate = () => getDate(stateParams) + const _getDate = () => getDate(stateParams, props.kingsService) return { ...stateParams, diff --git a/src/chronology/application/KingsService.ts b/src/chronology/application/KingsService.ts new file mode 100644 index 000000000..cd032d2bf --- /dev/null +++ b/src/chronology/application/KingsService.ts @@ -0,0 +1,58 @@ +import Promise from 'bluebird' +import KingsRepository from 'chronology/infrastructure/KingsRepository' +import { King } from 'chronology/ui/BrinkmanKings/BrinkmanKings' + +export interface kingsSearch { + fetchAll(query: string): Promise +} + +export default class KingsService implements kingsSearch { + private readonly kingsRepository: KingsRepository + private _kings: King[] = [] + kingOptions: Array<{ label: string; value: King }> = [] + + constructor(kingsRepository: KingsRepository) { + this.kingsRepository = kingsRepository + } + + static async createAndInitialize( + kingsRepository: KingsRepository + ): Promise { + const service = new KingsService(kingsRepository) + await service.initializeKings() + return service + } + + get kings(): King[] { + return this._kings + } + + private async initializeKings(): Promise { + try { + this._kings = await this.fetchAll() + this.kingOptions = this.getKingOptions() + } catch (error) { + console.error('Failed to initialize kingsCollection', error) + } + } + + fetchAll(): Promise { + return this.kingsRepository.fetchAll() + } + + private getKingOptions(): Array<{ label: string; value: King }> { + return this.kings + .filter((king) => !['16', '17'].includes(king.dynastyNumber)) + .map((king) => { + return { + label: this.getKingSelectLabel(king), + value: king, + } + }) + } + + private getKingSelectLabel(king: King): string { + const kingYears = king.date ? ` (${king.date})` : '' + return `${king.name}${kingYears}, ${king.dynastyName}` + } +} diff --git a/src/chronology/domain/Date.ts b/src/chronology/domain/Date.ts index af0e5db3b..4ea48f5b8 100644 --- a/src/chronology/domain/Date.ts +++ b/src/chronology/domain/Date.ts @@ -2,9 +2,13 @@ import { MesopotamianDateDto } from 'fragmentarium/domain/FragmentDtos' import _ from 'lodash' import { romanize } from 'romans' import { MesopotamianDateBase } from 'chronology/domain/DateBase' +import KingsService from 'chronology/application/KingsService' export class MesopotamianDate extends MesopotamianDateBase { - static fromJson(dateJSON: MesopotamianDateDto): MesopotamianDate { + static fromJson( + dateJson: MesopotamianDateDto, + kingsService: KingsService + ): MesopotamianDate { const { year, month, @@ -14,8 +18,9 @@ export class MesopotamianDate extends MesopotamianDateBase { isSeleucidEra, isAssyrianDate, ur3Calendar, - } = dateJSON + } = dateJson return new MesopotamianDate( + kingsService, year, month, day, diff --git a/src/chronology/domain/DateBase.ts b/src/chronology/domain/DateBase.ts index b1ac69da0..d481f32fd 100644 --- a/src/chronology/domain/DateBase.ts +++ b/src/chronology/domain/DateBase.ts @@ -3,6 +3,7 @@ import { Eponym } from 'chronology/ui/DateEditor/Eponyms' import DateConverter from 'chronology/domain/DateConverter' import data from 'chronology/domain/dateConverterData.json' import _ from 'lodash' +import KingsService from 'chronology/application/KingsService' export interface DateField { value: string @@ -40,6 +41,7 @@ export enum Ur3Calendar { } export class MesopotamianDateBase { + kingsService: KingsService year: DateField month: MonthField day: DateField @@ -50,6 +52,7 @@ export class MesopotamianDateBase { ur3Calendar?: Ur3Calendar constructor( + kingsService: KingsService, year: DateField, month: MonthField, day: DateField, @@ -59,6 +62,7 @@ export class MesopotamianDateBase { isAssyrianDate?: boolean, ur3Calendar?: Ur3Calendar ) { + this.kingsService = kingsService this.year = year this.month = month this.day = day @@ -188,7 +192,7 @@ export class MesopotamianDateBase { day: number, isApproximate: boolean ): string { - const converter = new DateConverter() + const converter = new DateConverter({ ...this }) converter.setToSeBabylonianDate(year, month, day) return this.insertDateApproximation(converter.toDateString(), isApproximate) } @@ -203,7 +207,7 @@ export class MesopotamianDateBase { (key) => data.rulerToBrinkmanKings[key] === this.king?.orderGlobal ) if (kingName) { - const converter = new DateConverter() + const converter = new DateConverter({ ...this }) converter.setToMesopotamianDate(kingName, year, month, day) return this.insertDateApproximation( converter.toDateString(), diff --git a/src/chronology/domain/DateConverter.ts b/src/chronology/domain/DateConverter.ts index 1c2b56f48..2cc5fb30d 100644 --- a/src/chronology/domain/DateConverter.ts +++ b/src/chronology/domain/DateConverter.ts @@ -11,15 +11,17 @@ import { King, findKingByOrderGlobal, } from 'chronology/ui/BrinkmanKings/BrinkmanKings' +import KingsService from 'chronology/application/KingsService' export default class DateConverter extends DateConverterBase { checks: DateConverterChecks = new DateConverterChecks() earliestDate: CalendarProps latestDate: CalendarProps + kingsService: KingsService - constructor() { + constructor({ kingsService }: { kingsService: KingsService }) { super() - this.setToEarliestDate() + this.kingsService = kingsService this.earliestDate = { ...this.calendar } this.setToLatestDate() this.latestDate = { ...this.calendar } @@ -49,7 +51,7 @@ export default class DateConverter extends DateConverterBase { ruler = ruler ?? this.calendar?.ruler if (ruler) { const orderGlobal = data.rulerToBrinkmanKings[ruler] - return findKingByOrderGlobal(orderGlobal) + return findKingByOrderGlobal(orderGlobal, this.kingsService.kings) } return null } diff --git a/src/chronology/infrastructure/KingsRepository.ts b/src/chronology/infrastructure/KingsRepository.ts new file mode 100644 index 000000000..07e69b8af --- /dev/null +++ b/src/chronology/infrastructure/KingsRepository.ts @@ -0,0 +1,15 @@ +import { King } from 'chronology/ui/BrinkmanKings/BrinkmanKings' +import Promise from 'bluebird' +import ApiClient from 'http/ApiClient' + +export default class KingsRepository { + private readonly apiClient: ApiClient + + constructor(apiClient: ApiClient) { + this.apiClient = apiClient + } + + fetchAll(): Promise { + return this.apiClient.fetchJson('/kings-all', false) + } +} diff --git a/src/chronology/ui/BrinkmanKings/BrinkmanKings.test.tsx b/src/chronology/ui/BrinkmanKings/BrinkmanKings.test.tsx index 89a251229..8225904e2 100644 --- a/src/chronology/ui/BrinkmanKings/BrinkmanKings.test.tsx +++ b/src/chronology/ui/BrinkmanKings/BrinkmanKings.test.tsx @@ -1,5 +1,19 @@ +import { render, screen } from '@testing-library/react' import BrinkmanKingsTable from 'chronology/ui/BrinkmanKings/BrinkmanKings' +/* +jest.doMock('chronology/domain/BrinkmanKings.json', () => ({ + __esModule: true, + default: [], +})) +*/ +jest.mock('chronology/domain/BrinkmanKings.json', () => [], { virtual: true }) + test('Snapshot', () => { expect(BrinkmanKingsTable()).toMatchSnapshot() }) + +test('Displays only kings from Brinkman in table', () => { + render(BrinkmanKingsTable()) + expect(screen.getByText('Maništušu')).not.toBeInTheDocument() +}) diff --git a/src/chronology/ui/BrinkmanKings/BrinkmanKings.tsx b/src/chronology/ui/BrinkmanKings/BrinkmanKings.tsx index c590bfce8..3d8a2b280 100644 --- a/src/chronology/ui/BrinkmanKings/BrinkmanKings.tsx +++ b/src/chronology/ui/BrinkmanKings/BrinkmanKings.tsx @@ -1,13 +1,12 @@ import React, { Fragment } from 'react' - -import Table from 'react-bootstrap/Table' import _ from 'lodash' import 'chronology/ui/BrinkmanKings/BrinkmanKings.sass' -import BrinkmanKings from 'chronology/domain/BrinkmanKings.json' +//import brinkmanKings from 'chronology/domain/BrinkmanKings.json' import { Popover } from 'react-bootstrap' import HelpTrigger from 'common/HelpTrigger' import Select, { ValueType } from 'react-select' import { KingDateField } from 'chronology/domain/DateBase' +import KingsService from 'chronology/application/KingsService' export interface King { orderGlobal: number @@ -19,16 +18,59 @@ export interface King { date: string totalOfYears: string notes: string + isNotInBrinkman?: boolean } -const dynasties: string[] = _.uniq(_.map(BrinkmanKings, 'dynastyName')) +// ToDo: +// Ensure that the data is correctly passed. +// Perhaps it would make sense to create a class +// which fetches the kings once on initiation, +// then computes the rest of the variables. +export class KingsCollection { + readonly kings: readonly King[] + readonly dynasties: readonly string[] -function getKingsByDynasty(dynastyName: string): King[] | KingDateField[] { - return _.filter(BrinkmanKings, ['dynastyName', dynastyName]) + constructor(kings: readonly King[]) { + this.kings = kings + this.dynasties = _.uniq(_.map(this.kings, 'dynastyName')) + } +} + +export function KingField({ + king, + setKing, + setIsCalenderFieldDisplayed, + kingsService, +}: { + readonly king?: King | KingDateField + readonly setKing: React.Dispatch> + readonly setIsCalenderFieldDisplayed?: React.Dispatch< + React.SetStateAction + > + kingsService: KingsService +}): JSX.Element { + return ( + - onKingFieldChange(option, setKing, setIsCalenderFieldDisplayed) - } - isSearchable={true} - autoFocus={true} - placeholder="King" - value={king ? getCurrentKingOption(king) : undefined} - /> - ) -} - const onKingFieldChange = ( option: ValueType<{ label: string; value: King }, false>, setKing: React.Dispatch>, @@ -139,24 +153,23 @@ const onKingFieldChange = ( } } -function getKingSelectLabel(king: King): string { - const kingYears = king.date ? ` (${king.date})` : '' - return `${king.name}${kingYears}, ${king.dynastyName}` -} - -function getKingOptions(): Array<{ label: string; value: King }> { - return BrinkmanKings.filter( - (king) => !['16', '17'].includes(king.dynastyNumber) - ).map((king) => { - return { - label: getKingSelectLabel(king), - value: king, - } - }) -} - function getCurrentKingOption( - king?: King + kingOptions: { + label: string + value: King + }[], + king?: King | KingDateField ): { label: string; value: King } | undefined { + if (king && ('isBroken' in king || 'isUncertain' in king)) { + const { isBroken, isUncertain, ..._king } = king + king = _king + } return kingOptions.find((kingOption) => _.isEqual(kingOption.value, king)) } + +function getKingsByDynasty( + dynastyName: string, + BrinkmanKings: readonly King[] +): King[] | KingDateField[] { + return _.filter(BrinkmanKings, ['dynastyName', dynastyName]) +} diff --git a/src/chronology/ui/BrinkmanKings/BrinkmanKingsTable.tsx b/src/chronology/ui/BrinkmanKings/BrinkmanKingsTable.tsx index 10cc4a5b9..33193c10f 100644 --- a/src/chronology/ui/BrinkmanKings/BrinkmanKingsTable.tsx +++ b/src/chronology/ui/BrinkmanKings/BrinkmanKingsTable.tsx @@ -1,8 +1,45 @@ import React from 'react' import { Markdown } from 'common/Markdown' -import BrinkmanKingsTable from 'chronology/ui/BrinkmanKings/BrinkmanKings' +import { KingsCollection, getDynasty } from './BrinkmanKings' +import { Table } from 'react-bootstrap' +import KingsService from 'chronology/application/KingsService' +import withData from 'http/withData' -export default function AboutListOfKings(): JSX.Element { +function BrinkmanKingsTable({ + kingsCollection, +}: { + kingsCollection: KingsCollection +}): JSX.Element { + return ( + + + {kingsCollection.dynasties.map((dynastyName, index) => + getDynasty(dynastyName, index, kingsCollection.kings, true) + )} + +
+ ) +} + +const BrinkmanKingsTableWithData = withData< + unknown, + { + kingsService: KingsService + }, + { kingsCollection: KingsCollection } +>( + (props) => { + return + }, + (props) => + props.kingsService.fetchAll().then((kings) => ({ + kingsCollection: new KingsCollection(kings), + })) +) + +export default function AboutListOfKings( + kingsService: KingsService +): JSX.Element { return ( <> - + ) } diff --git a/src/chronology/ui/DateConverter/DateConverterForm.tsx b/src/chronology/ui/DateConverter/DateConverterForm.tsx index 292bb04a0..caca1d6b5 100644 --- a/src/chronology/ui/DateConverter/DateConverterForm.tsx +++ b/src/chronology/ui/DateConverter/DateConverterForm.tsx @@ -12,6 +12,7 @@ import { } from 'chronology/ui/DateConverter/DateConverterFormParts' import { CalendarProps } from 'chronology/domain/DateConverterBase' import { handleDateConverterFormChange } from 'chronology/application/DateConverterFormChange' +import KingsService from 'chronology/application/KingsService' // ToDo: // - Errors: @@ -95,8 +96,12 @@ function useConverterFormMethods( } } -function useConverterForm(): FormProps { - const [dateConverter] = useState(() => new DateConverter()) +function useConverterForm({ + kingsService, +}: { + kingsService: KingsService +}): FormProps { + const [dateConverter] = useState(() => new DateConverter({ kingsService })) const [formData, setFormData] = useState(dateConverter.calendar) const [scenario, setScenario] = useState('setToGregorianDate') const converterFormState = { @@ -141,8 +146,12 @@ function DateConverterFormControlsContent(params: FormProps): JSX.Element { ) } -function DateConverterForm(): JSX.Element { - const params = useConverterForm() +function DateConverterForm({ + kingsService, +}: { + kingsService: KingsService +}): JSX.Element { + const params = useConverterForm({ kingsService }) return ( <> diff --git a/src/chronology/ui/DateEditor/DateSelectionInput.tsx b/src/chronology/ui/DateEditor/DateSelectionInput.tsx index 5cb66218b..f91f9a32b 100644 --- a/src/chronology/ui/DateEditor/DateSelectionInput.tsx +++ b/src/chronology/ui/DateEditor/DateSelectionInput.tsx @@ -11,6 +11,7 @@ import { RadioButton, getBrokenAndUncertainSwitches, } from 'chronology/ui/DateEditor/DateSelectionInputBase' +import KingsService from 'chronology/application/KingsService' type InputGroupsProps = { yearValue: string @@ -40,6 +41,7 @@ export interface DateOptionsProps { king?: KingDateField | King kingBroken?: boolean kingUncertain?: boolean + kingsService: KingsService eponym?: Eponym eponymBroken?: boolean eponymUncertain?: boolean diff --git a/src/chronology/ui/DateEditor/DatesInTextSelection.tsx b/src/chronology/ui/DateEditor/DatesInTextSelection.tsx index 0e626ec27..6c219b7f9 100644 --- a/src/chronology/ui/DateEditor/DatesInTextSelection.tsx +++ b/src/chronology/ui/DateEditor/DatesInTextSelection.tsx @@ -7,12 +7,14 @@ import { Session } from 'auth/Session' import { Button } from 'react-bootstrap' import SessionContext from 'auth/SessionContext' import classNames from 'classnames' +import KingsService from 'chronology/application/KingsService' interface Props { datesInText: readonly MesopotamianDate[] updateDatesInText: ( datesInText: readonly MesopotamianDate[] ) => Bluebird + kingsService: KingsService } interface DatesInTextSelectionAttrs { @@ -38,7 +40,9 @@ interface DatesInTextSelectionMethods { interface DatesInTextSelectionState extends DatesInTextSelectionAttrs, - DatesInTextSelectionMethods {} + DatesInTextSelectionMethods { + kingsService: KingsService +} async function updateDateInArray({ updateDatesInText, @@ -98,6 +102,7 @@ const saveDates = async ({ function useDateInTextSelectionState({ datesInText, updateDatesInText, + kingsService, }: Props): DatesInTextSelectionState { const [newDate, setNewDate] = useState( undefined @@ -117,6 +122,7 @@ function useDateInTextSelectionState({ setIsSaving, setNewDate, setDatesInTextDisplay, + kingsService, } return { @@ -147,6 +153,7 @@ const getDateEditor = ( setIsSaving={state.setIsSaving} setDate={state.setNewDate} saveDateOverride={state.saveDates} + kingsService={state.kingsService} /> ) @@ -179,9 +186,14 @@ const getAddButton = ( export default function DatesInTextSelection({ datesInText = [], updateDatesInText, + kingsService, }: Props): JSX.Element { const target = useRef(null) - const state = useDateInTextSelectionState({ datesInText, updateDatesInText }) + const state = useDateInTextSelectionState({ + datesInText, + updateDatesInText, + kingsService, + }) return ( <> Dates in text: {getAddButton(target, state)} @@ -194,6 +206,7 @@ export default function DatesInTextSelection({ inList={true} index={index} saveDateOverride={state.saveDates} + kingsService={kingsService} /> ) })} diff --git a/src/chronology/ui/DateEditor/Eponyms.tsx b/src/chronology/ui/DateEditor/Eponyms.tsx index 7784aa849..149c85152 100644 --- a/src/chronology/ui/DateEditor/Eponyms.tsx +++ b/src/chronology/ui/DateEditor/Eponyms.tsx @@ -5,6 +5,7 @@ import _eponymsMiddleAssyrian from 'chronology/domain/EponymsMiddleAssyrian.json import _eponymsOldAssyrian from 'chronology/domain/EponymsOldAssyrian.json' import Select from 'react-select' import _ from 'lodash' +import { EponymDateField } from 'chronology/domain/DateBase' export interface Eponym { readonly date?: string @@ -38,7 +39,7 @@ export function EponymField({ assyrianPhase, setEponym, }: { - eponym?: Eponym + eponym?: Eponym | EponymDateField assyrianPhase: 'NA' | 'MA' | 'OA' setEponym: React.Dispatch> }): JSX.Element { @@ -82,8 +83,12 @@ function getEponymsOptions(): Array<{ label: string; value: Eponym }> { } function getCurrentEponymOption( - eponym?: Eponym + eponym?: Eponym | EponymDateField ): { label: string; value: Eponym } | undefined { + if (eponym && ('isBroken' in eponym || 'isUncertain' in eponym)) { + const { isBroken, isUncertain, ..._eponym } = eponym + eponym = _eponym + } return eponymOptions.find((eponymOption) => _.isEqual(eponymOption.value, eponym) ) diff --git a/src/fragmentarium/infrastructure/FragmentRepository.ts b/src/fragmentarium/infrastructure/FragmentRepository.ts index f02d5f820..d7b07c422 100644 --- a/src/fragmentarium/infrastructure/FragmentRepository.ts +++ b/src/fragmentarium/infrastructure/FragmentRepository.ts @@ -54,6 +54,7 @@ import { createArchaeology, } from 'fragmentarium/domain/archaeology' import { JsonApiClient } from 'index' +import KingsService from 'chronology/application/KingsService' export function createScript(dto: ScriptDto): Script { return { @@ -83,7 +84,10 @@ export function createJoins(joins): Joins { ) } -function createFragment(dto: FragmentDto): Fragment { +function createFragment( + dto: FragmentDto, + kingsService: KingsService +): Fragment { return Fragment.create({ ...dto, number: museumNumberToString(dto.museumNumber), @@ -104,9 +108,13 @@ function createFragment(dto: FragmentDto): Fragment { genres: Genres.fromJson(dto.genres), script: createScript(dto.script), projects: dto.projects.map(createResearchProject), - date: dto.date ? MesopotamianDate.fromJson(dto.date) : undefined, + date: dto.date + ? MesopotamianDate.fromJson(dto.date, kingsService) + : undefined, datesInText: dto.datesInText - ? dto.datesInText.map((date) => MesopotamianDate.fromJson(date)) + ? dto.datesInText.map((date) => + MesopotamianDate.fromJson(date, kingsService) + ) : [], archaeology: dto.archaeology ? createArchaeology(dto.archaeology) @@ -142,7 +150,13 @@ function createQueryResult(dto): QueryResult { class ApiFragmentRepository implements FragmentInfoRepository, FragmentRepository, AnnotationRepository { - constructor(private readonly apiClient: JsonApiClient) {} + kingsService: KingsService + constructor( + private readonly apiClient: JsonApiClient, + kingsService: KingsService + ) { + this.kingsService = kingsService + } statistics(): Promise<{ transliteratedFragments: number; lines: number }> { return this.apiClient.fetchJson(`/statistics`, false) @@ -170,7 +184,7 @@ class ApiFragmentRepository }`, false ) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } random(): FragmentInfosPromise { @@ -209,7 +223,7 @@ class ApiFragmentRepository .postJson(path, { genres: genres.genres, }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } updateScript(number: string, script: Script): Promise { @@ -222,12 +236,14 @@ class ApiFragmentRepository uncertain: script.uncertain, }, }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } updateDate(number: string, date: MesopotamianDate): Promise { const path = createFragmentPath(number, 'date') - return this.apiClient.postJson(path, { date }).then(createFragment) + return this.apiClient + .postJson(path, { date }) + .then((result) => createFragment(result, this.kingsService)) } updateDatesInText( @@ -235,7 +251,9 @@ class ApiFragmentRepository datesInText: readonly MesopotamianDate[] ): Promise { const path = createFragmentPath(number, 'dates-in-text') - return this.apiClient.postJson(path, { datesInText }).then(createFragment) + return this.apiClient + .postJson(path, { datesInText }) + .then((result) => createFragment(result, this.kingsService)) } updateTransliteration( @@ -247,7 +265,7 @@ class ApiFragmentRepository .postJson(path, { transliteration: transliteration, }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } updateIntroduction(number: string, introduction: string): Promise { @@ -256,7 +274,7 @@ class ApiFragmentRepository .postJson(path, { introduction: introduction, }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } updateNotes(number: string, notes: string): Promise { @@ -265,7 +283,7 @@ class ApiFragmentRepository .postJson(path, { notes: notes, }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } updateLemmatization( @@ -275,14 +293,14 @@ class ApiFragmentRepository const path = createFragmentPath(number, 'lemmatization') return this.apiClient .postJson(path, { lemmatization: lemmatization }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } updateReferences(number: string, references: Reference[]): Promise { const path = createFragmentPath(number, 'references') return this.apiClient .postJson(path, { references: references }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } updateArchaeology( @@ -292,7 +310,7 @@ class ApiFragmentRepository const path = createFragmentPath(number, 'archaeology') return this.apiClient .postJson(path, { archaeology: archaeology }) - .then(createFragment) + .then((result) => createFragment(result, this.kingsService)) } folioPager(folio: Folio, number: string): Promise { diff --git a/src/fragmentarium/ui/fragment/CuneiformFragment.tsx b/src/fragmentarium/ui/fragment/CuneiformFragment.tsx index 5c3c79db7..8197308c8 100644 --- a/src/fragmentarium/ui/fragment/CuneiformFragment.tsx +++ b/src/fragmentarium/ui/fragment/CuneiformFragment.tsx @@ -26,6 +26,7 @@ import ArchaeologyEditor from 'fragmentarium/ui/fragment/ArchaeologyEditor' import { ArchaeologyDto } from 'fragmentarium/domain/archaeology' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' +import KingsService from 'chronology/application/KingsService' const ContentSection: FunctionComponent = ({ children, @@ -188,6 +189,7 @@ type CuneiformFragmentProps = { saving: boolean error: Error | null activeLine: string + kingsService: KingsService } const CuneiformFragment: FunctionComponent = ({ fragment, @@ -196,6 +198,7 @@ const CuneiformFragment: FunctionComponent = ({ afoRegisterService, wordService, findspotService, + kingsService, activeFolio, tab, onSave, @@ -213,6 +216,7 @@ const CuneiformFragment: FunctionComponent = ({ fragmentService={fragmentService} afoRegisterService={afoRegisterService} onSave={onSave} + kingsService={kingsService} /> @@ -256,6 +260,7 @@ type ControllerProps = { fragmentService: FragmentService fragmentSearchService: FragmentSearchService afoRegisterService: AfoRegisterService + kingsService: KingsService wordService: WordService findspotService: FindspotService activeFolio?: Folio | null @@ -272,6 +277,7 @@ const CuneiformFragmentController: FunctionComponent = ({ activeFolio = null, tab = null, activeLine, + kingsService, }: ControllerProps) => { const [currentFragment, setFragment] = useState(fragment) const [isSaving, setIsSaving] = useState(false) @@ -312,6 +318,7 @@ const CuneiformFragmentController: FunctionComponent = ({ saving={isSaving} error={error} activeLine={activeLine} + kingsService={kingsService} /> ) diff --git a/src/fragmentarium/ui/fragment/FragmentView.tsx b/src/fragmentarium/ui/fragment/FragmentView.tsx index 27bac7a42..2d7a961d0 100644 --- a/src/fragmentarium/ui/fragment/FragmentView.tsx +++ b/src/fragmentarium/ui/fragment/FragmentView.tsx @@ -19,6 +19,7 @@ import { Session } from 'auth/Session' import { HeadTags } from 'router/head' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' +import KingsService from 'chronology/application/KingsService' function TagSignsButton({ number, @@ -43,6 +44,7 @@ type Props = { wordService: WordService findspotService: FindspotService afoRegisterService: AfoRegisterService + kingsService: KingsService number: string folioName: string | null folioNumber: string | null @@ -75,6 +77,7 @@ function FragmentView({ folioNumber, tab, activeLine, + kingsService, }: Props): JSX.Element { const activeFolio = createActiveFolio(folioName, folioNumber) @@ -111,6 +114,7 @@ function FragmentView({ fragmentSearchService={fragmentSearchService} wordService={wordService} findspotService={findspotService} + kingsService={kingsService} activeFolio={activeFolio} tab={tab} activeLine={activeLine} diff --git a/src/fragmentarium/ui/info/Details.tsx b/src/fragmentarium/ui/info/Details.tsx index 7ce8d9674..b56ea424b 100644 --- a/src/fragmentarium/ui/info/Details.tsx +++ b/src/fragmentarium/ui/info/Details.tsx @@ -13,6 +13,7 @@ import FragmentService from 'fragmentarium/application/FragmentService' import Bluebird from 'bluebird' import { MesopotamianDate } from 'chronology/domain/Date' import DatesInTextSelection from 'chronology/ui/DateEditor/DatesInTextSelection' +import KingsService from 'chronology/application/KingsService' interface Props { readonly fragment: Fragment @@ -102,6 +103,7 @@ interface DetailsProps { datesInText: readonly MesopotamianDate[] ) => Bluebird readonly fragmentService: FragmentService + readonly kingsService: KingsService } function Details({ @@ -111,6 +113,7 @@ function Details({ updateDate, updateDatesInText, fragmentService, + kingsService, }: DetailsProps): JSX.Element { return (
    @@ -150,12 +153,17 @@ function Details({ />
  • - +
diff --git a/src/fragmentarium/ui/info/Info.tsx b/src/fragmentarium/ui/info/Info.tsx index aad24fc6a..f07003d9e 100644 --- a/src/fragmentarium/ui/info/Info.tsx +++ b/src/fragmentarium/ui/info/Info.tsx @@ -17,12 +17,14 @@ import { ProjectList } from 'fragmentarium/ui/info/ResearchProjects' import _ from 'lodash' import AfoRegisterService from 'afo-register/application/AfoRegisterService' import AfoRegisterFragmentRecords from 'afo-register/ui/AfoRegisterFragmentRecords' +import KingsService from 'chronology/application/KingsService' interface Props { fragment: Fragment fragmentService: FragmentService afoRegisterService: AfoRegisterService onSave: (fragment: Promise) => void + kingsService: KingsService } export default function Info({ @@ -30,6 +32,7 @@ export default function Info({ fragmentService, afoRegisterService, onSave, + kingsService, }: Props): JSX.Element { const updateGenres = (genres: Genres) => onSave(fragmentService.updateGenres(fragment.number, genres)) @@ -48,6 +51,7 @@ export default function Info({ updateDate={updateDate} updateDatesInText={updateDatesInText} fragmentService={fragmentService} + kingsService={kingsService} />
diff --git a/src/index.tsx b/src/index.tsx index 8998d7064..f4d9a2c4a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren } from 'react' +import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react' import ReactDOM from 'react-dom' import { BrowserRouter as Router, useHistory } from 'react-router-dom' import Promise from 'bluebird' @@ -24,10 +24,12 @@ import { scopeString, useAuthentication } from 'auth/Auth' import SignService from 'signs/application/SignService' import SignRepository from 'signs/infrastructure/SignRepository' import AfoRegisterRepository from 'afo-register/infrastructure/AfoRegisterRepository' +import KingsRepository from 'chronology/infrastructure/KingsRepository' import MarkupService, { CachedMarkupService, } from 'markup/application/MarkupService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' +import KingsService from 'chronology/application/KingsService' import './index.sass' import { FindspotService } from 'fragmentarium/application/FindspotService' import { ApiFindspotRepository } from 'fragmentarium/infrastructure/FindspotRepository' @@ -56,38 +58,115 @@ export type JsonApiClient = { function InjectedApp(): JSX.Element { const authenticationService = useAuthentication() - const apiClient = new ApiClient(authenticationService, errorReporter) - const wordRepository = new WordRepository(apiClient) - const signsRepository = new SignRepository(apiClient) - const fragmentRepository = new FragmentRepository(apiClient) - const imageRepository = new ApiImageRepository(apiClient) - const bibliographyRepository = new BibliographyRepository(apiClient) - const afoRegisterRepository = new AfoRegisterRepository(apiClient) - const findspotRepository = new ApiFindspotRepository(apiClient) - - const bibliographyService = new BibliographyService(bibliographyRepository) - const fragmentService = new FragmentService( - fragmentRepository, - imageRepository, - wordRepository, - bibliographyService + const apiClient = useMemo( + () => new ApiClient(authenticationService, errorReporter), + [authenticationService] ) - const fragmentSearchService = new FragmentSearchService(fragmentRepository) - const wordService = new WordService(wordRepository) - const textService = new TextService( - apiClient, + const [kingsService, setKingsService] = useState(null) + const [services, setServices] = useState<{ + fragmentService?: FragmentService + wordService?: WordService + signService?: SignService + bibliographyService?: BibliographyService + textService?: TextService + markupService?: MarkupService + cachedMarkupService?: CachedMarkupService + afoRegisterService?: AfoRegisterService + findspotService?: FindspotService + fragmentSearchService?: FragmentSearchService + }>({}) + + useEffect(() => { + async function initializeKingService() { + const kingsRepository = new KingsRepository(apiClient) + const initializedKingsService = await KingsService.createAndInitialize( + kingsRepository + ) + setKingsService(initializedKingsService) + } + initializeKingService() + }, [apiClient]) + + useEffect(() => { + if (kingsService) { + const wordRepository = new WordRepository(apiClient) + const signsRepository = new SignRepository(apiClient, kingsService) + const imageRepository = new ApiImageRepository(apiClient) + const bibliographyRepository = new BibliographyRepository(apiClient) + const afoRegisterRepository = new AfoRegisterRepository(apiClient) + const findspotRepository = new ApiFindspotRepository(apiClient) + const bibliographyService = new BibliographyService( + bibliographyRepository + ) + const fragmentRepository = new FragmentRepository(apiClient, kingsService) + const fragmentService = new FragmentService( + fragmentRepository, + imageRepository, + wordRepository, + bibliographyService + ) + const fragmentSearchService = new FragmentSearchService( + fragmentRepository + ) + const wordService = new WordService(wordRepository) + const signService = new SignService(signsRepository) + const textService = new TextService( + apiClient, + fragmentService, + wordService, + bibliographyService + ) + const markupService = new MarkupService(apiClient, bibliographyService) + const cachedMarkupService = new CachedMarkupService( + apiClient, + bibliographyService + ) + const afoRegisterService = new AfoRegisterService(afoRegisterRepository) + const findspotService = new FindspotService(findspotRepository) + setServices({ + fragmentService, + wordService, + signService, + bibliographyService, + textService, + markupService, + cachedMarkupService, + afoRegisterService, + findspotService, + fragmentSearchService, + }) + } + }, [apiClient, kingsService]) + + const { fragmentService, wordService, - bibliographyService - ) - const signService = new SignService(signsRepository) - const markupService = new MarkupService(apiClient, bibliographyService) - const cachedMarkupService = new CachedMarkupService( - apiClient, - bibliographyService - ) - const afoRegisterService = new AfoRegisterService(afoRegisterRepository) - const findspotService = new FindspotService(findspotRepository) + signService, + bibliographyService, + textService, + markupService, + cachedMarkupService, + afoRegisterService, + findspotService, + fragmentSearchService, + } = services + + if ( + !kingsService || + !fragmentService || + !wordService || + !signService || + !fragmentSearchService || + !bibliographyService || + !textService || + !markupService || + !cachedMarkupService || + !afoRegisterService || + !findspotService + ) { + return
Loading...
+ } + return ( ) } diff --git a/src/router/fragmentariumRoutes.tsx b/src/router/fragmentariumRoutes.tsx index 5ee436e67..b396ccd7a 100644 --- a/src/router/fragmentariumRoutes.tsx +++ b/src/router/fragmentariumRoutes.tsx @@ -22,6 +22,7 @@ import { HeadTagsService } from 'router/head' import BibliographyService from 'bibliography/application/BibliographyService' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' +import KingsService from 'chronology/application/KingsService' function parseStringParam(location: Location, param: string): string | null { const value = parse(location.search)[param] @@ -62,6 +63,7 @@ export default function FragmentariumRoutes({ signService, bibliographyService, fragmentSlugs, + kingsService, }: { sitemap: boolean fragmentService: FragmentService @@ -72,6 +74,7 @@ export default function FragmentariumRoutes({ afoRegisterService: AfoRegisterService signService: SignService bibliographyService: BibliographyService + kingsService: KingsService fragmentSlugs?: FragmentSlugs }): JSX.Element[] { return [ @@ -142,6 +145,7 @@ export default function FragmentariumRoutes({ wordService={wordService} findspotService={findspotService} afoRegisterService={afoRegisterService} + kingsService={kingsService} session={session} {...parseFragmentParams(match, location)} /> diff --git a/src/router/router.tsx b/src/router/router.tsx index 2577bcb17..62ece8aa5 100644 --- a/src/router/router.tsx +++ b/src/router/router.tsx @@ -27,6 +27,7 @@ import Header from 'Header' import { helmetContext } from 'router/head' import { HelmetProvider } from 'react-helmet-async' import { FindspotService } from 'fragmentarium/application/FindspotService' +import KingsService from 'chronology/application/KingsService' export interface Services { wordService: WordService @@ -39,6 +40,7 @@ export interface Services { cachedMarkupService: CachedMarkupService afoRegisterService: AfoRegisterService findspotService: FindspotService + kingsService: KingsService } export default function Router(services: Services): JSX.Element { diff --git a/src/router/toolsRoutes.tsx b/src/router/toolsRoutes.tsx index 33b2c9dac..269c8fcf6 100644 --- a/src/router/toolsRoutes.tsx +++ b/src/router/toolsRoutes.tsx @@ -17,6 +17,7 @@ import DateConverterForm, { import AboutListOfKings from 'chronology/ui/BrinkmanKings/BrinkmanKingsTable' import _ from 'lodash' import 'about/ui/about.sass' +import KingsService from 'chronology/application/KingsService' const tabIds = ['date-converter', 'list-of-kings'] as const type TabId = typeof tabIds[number] @@ -24,9 +25,11 @@ type TabId = typeof tabIds[number] const Tools = ({ markupService, activeTab, + kingsService, }: { markupService: MarkupService activeTab: TabId + kingsService: KingsService }): JSX.Element => { const history = useHistory() const [selectedTab, setSelectedTab] = useState(activeTab) @@ -51,10 +54,10 @@ const Tools = ({ > {AboutDateConverter(markupService)} - + - {AboutListOfKings()} + {AboutListOfKings(kingsService)} @@ -64,9 +67,11 @@ const Tools = ({ export default function ToolsRoutes({ sitemap, markupService, + kingsService, }: { sitemap: boolean markupService: MarkupService + kingsService: KingsService }): JSX.Element[] { return [ diff --git a/src/signs/infrastructure/SignRepository.ts b/src/signs/infrastructure/SignRepository.ts index db1e6fa22..a2cbd1cad 100644 --- a/src/signs/infrastructure/SignRepository.ts +++ b/src/signs/infrastructure/SignRepository.ts @@ -7,12 +7,15 @@ import { AnnotationTokenType } from 'fragmentarium/domain/annotation' import { CroppedAnnotation } from 'signs/domain/CroppedAnnotation' import _ from 'lodash' import { MesopotamianDate } from 'chronology/domain/Date' +import KingsService from 'chronology/application/KingsService' class SignRepository { private readonly apiClient + readonly kingsService - constructor(apiClient: ApiClient) { + constructor(apiClient: ApiClient, kingsService: KingsService) { this.apiClient = apiClient + this.kingsService = kingsService } private handleEmptySignSearchResults( @@ -57,7 +60,8 @@ class SignRepository { return croppedAnnotations.map((croppedAnnotation) => { if (!_.isEmpty(croppedAnnotation.date)) { croppedAnnotation.date = MesopotamianDate.fromJson( - croppedAnnotation.date + croppedAnnotation.date, + this.kingsService ) } else { croppedAnnotation.date = undefined diff --git a/src/test-support/AppDriver.tsx b/src/test-support/AppDriver.tsx index 30d04625a..15fb4f933 100644 --- a/src/test-support/AppDriver.tsx +++ b/src/test-support/AppDriver.tsx @@ -34,10 +34,14 @@ import AfoRegisterRepository from 'afo-register/infrastructure/AfoRegisterReposi import AfoRegisterService from 'afo-register/application/AfoRegisterService' import { FindspotService } from 'fragmentarium/application/FindspotService' import { ApiFindspotRepository } from 'fragmentarium/infrastructure/FindspotRepository' +import KingsService from 'chronology/application/KingsService' +import KingsRepository from 'chronology/infrastructure/KingsRepository' function createApp(api): JSX.Element { + const kingsRepository = new KingsRepository(api) + const kingsService = new KingsService(kingsRepository) const wordRepository = new WordRepository(api) - const fragmentRepository = new FragmentRepository(api) + const fragmentRepository = new FragmentRepository(api, kingsService) const imageRepository = new ApiImageRepository(api) const bibliographyRepository = new BibliographyRepository(api) const findspotRepository = new ApiFindspotRepository(api) @@ -57,7 +61,7 @@ function createApp(api): JSX.Element { wordService, bibliographyService ) - const signsRepository = new SignRepository(api) + const signsRepository = new SignRepository(api, kingsService) const afoRegisterRepository = new AfoRegisterRepository(api) const signService = new SignService(signsRepository) const markupService = new MarkupService(api, bibliographyService) @@ -76,6 +80,7 @@ function createApp(api): JSX.Element { cachedMarkupService={cachedMarkupService} afoRegisterService={afoRegisterService} findspotService={findspotService} + kingsService={kingsService} /> ) }