Skip to content

Commit

Permalink
Merge pull request #108 from CentreForDigitalHumanities/feature/agent…
Browse files Browse the repository at this point in the history
…-form

Feature/agent form
  • Loading branch information
lukavdplas authored Aug 21, 2024
2 parents fc2a9ce + 5a281bd commit dd3fb51
Show file tree
Hide file tree
Showing 18 changed files with 553 additions and 22 deletions.
5 changes: 5 additions & 0 deletions backend/person/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class AgentDescriptionLocationAdmin(admin.StackedInline):

@admin.register(models.AgentDescription)
class AgentDescriptionAdmin(core_admin.EntityDescriptionAdmin, admin.ModelAdmin):
fieldsets = [
core_admin.named_fieldset,
("Person/group", {"fields": ["is_group"]}),
core_admin.description_source_fieldset,
]
inlines = [
AgentDescriptionGenderAdmin,
AgentDescriptionLocationAdmin,
Expand Down
101 changes: 100 additions & 1 deletion frontend/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,12 +800,33 @@ export type UserType = {
lastName: Scalars['String']['output'];
};

export type DataEntryAgentDescriptionQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;


export type DataEntryAgentDescriptionQuery = { __typename?: 'Query', agentDescription?: { __typename?: 'AgentDescriptionType', id: string, isGroup: boolean, designators: Array<string>, gender?: { __typename?: 'AgentDescriptionGenderType', id: string, gender: PersonAgentDescriptionGenderGenderChoices, sourceMention?: PersonAgentDescriptionGenderSourceMentionChoices | null, note: string } | null, location?: { __typename?: 'AgentDescriptionLocationType', id: string, sourceMention?: PersonAgentDescriptionLocationSourceMentionChoices | null, note: string, location: { __typename?: 'SpaceDescriptionType', id: string } } | null, source: { __typename?: 'SourceType', id: string } } | null };

export type LocationsInSourceListQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;


export type LocationsInSourceListQuery = { __typename?: 'Query', spaceDescriptions: Array<{ __typename?: 'SpaceDescriptionType', id: string, name: string }> };

export type DataEntryAgentIdentificationQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;


export type DataEntryAgentIdentificationQuery = { __typename?: 'Query', agentDescription?: { __typename?: 'AgentDescriptionType', id: string, name: string, description: string, isGroup: boolean } | null };

export type DataEntryAgentQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;


export type DataEntryAgentQuery = { __typename?: 'Query', agentDescription?: { __typename?: 'AgentDescriptionType', id: string, name: string, description: string, source: { __typename?: 'SourceType', id: string, name: string } } | null };
export type DataEntryAgentQuery = { __typename?: 'Query', agentDescription?: { __typename?: 'AgentDescriptionType', id: string, name: string, description: string, isGroup: boolean, source: { __typename?: 'SourceType', id: string, name: string } } | null };

export type DataEntryEpisodeContentsQueryVariables = Exact<{
id: Scalars['ID']['input'];
Expand Down Expand Up @@ -875,12 +896,90 @@ export type DataEntrySourceListQueryVariables = Exact<{ [key: string]: never; }>

export type DataEntrySourceListQuery = { __typename?: 'Query', sources: Array<{ __typename?: 'SourceType', id: string, name: string, editionAuthor: string, editionTitle: string, medievalAuthor: string, medievalTitle: string, numOfEpisodes: number }> };

export const DataEntryAgentDescriptionDocument = gql`
query DataEntryAgentDescription($id: ID!) {
agentDescription(id: $id) {
id
isGroup
designators
gender {
id
gender
sourceMention
note
}
location {
id
sourceMention
note
location {
id
}
}
source {
id
}
}
}
`;

@Injectable({
providedIn: 'root'
})
export class DataEntryAgentDescriptionGQL extends Apollo.Query<DataEntryAgentDescriptionQuery, DataEntryAgentDescriptionQueryVariables> {
override document = DataEntryAgentDescriptionDocument;

constructor(apollo: Apollo.Apollo) {
super(apollo);
}
}
export const LocationsInSourceListDocument = gql`
query LocationsInSourceList($id: ID!) {
spaceDescriptions(sourceId: $id) {
id
name
}
}
`;

@Injectable({
providedIn: 'root'
})
export class LocationsInSourceListGQL extends Apollo.Query<LocationsInSourceListQuery, LocationsInSourceListQueryVariables> {
override document = LocationsInSourceListDocument;

constructor(apollo: Apollo.Apollo) {
super(apollo);
}
}
export const DataEntryAgentIdentificationDocument = gql`
query DataEntryAgentIdentification($id: ID!) {
agentDescription(id: $id) {
id
name
description
isGroup
}
}
`;

@Injectable({
providedIn: 'root'
})
export class DataEntryAgentIdentificationGQL extends Apollo.Query<DataEntryAgentIdentificationQuery, DataEntryAgentIdentificationQueryVariables> {
override document = DataEntryAgentIdentificationDocument;

constructor(apollo: Apollo.Apollo) {
super(apollo);
}
}
export const DataEntryAgentDocument = gql`
query DataEntryAgent($id: ID!) {
agentDescription(id: $id) {
id
name
description
isGroup
source {
id
name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<form>
<h3>Designators</h3>

<p>What expressions are used in the text to refer to this agent?</p>

<lc-designators-control [formControl]="form.controls.designators"></lc-designators-control>

<h3>Gender</h3>

<p>Does the text establish the gender of this agent?</p>

<div class="mb-4">
<label class="form-label" for="input-gender">Gender</label>
<select class="form-select" id="input-gender"
[formControl]="form.controls.gender.controls.gender">
<option *ngFor="let option of genderOptions" [value]="option.value">
{{option.label}}
</option>
</select>
</div>

<div class="mb-4">
<label class="form-label" for="input-gender-mention">
How is this information presented in the text?
</label>
<select class="form-select" id="input-gender-mention"
[formControl]="form.controls.gender.controls.sourceMention">
<option *ngFor="let option of genderSourceMentionOptions" [value]="option.value">
{{option.label}}
</option>
</select>
</div>

<div class="mb-4">
<label class="form-label" for="input-gender-notes">
Additional notes
</label>
<textarea class="form-control" id="input-gender-notes"
[formControl]="form.controls.gender.controls.note">
</textarea>
</div>

<ng-container *ngIf="isGroup$ | async">
<h3>Location</h3>

<div class="mb-4">
<label class="form-label" for="input-has-location">
Is the group characterised by their location? (For example, "the citizens of
Arles", "the nuns at the abbey in Poitiers".)
</label>
<select class="form-select" id="input-has-location"
[formControl]="form.controls.location.controls.hasLocation">
<option [ngValue]="true">Yes</option>
<option [ngValue]="false">No</option>
</select>
</div>

<div [ngbCollapse]="!form.controls.location.controls.hasLocation.value">
<div class="mb-4">
<label class="form-label" for="input-location">
By which location is the group defined?
</label>
<select class="form-select" id="input-location"
[formControl]="form.controls.location.controls.location">
<ng-container *ngIf="locations$ | async as locationData">
<option *ngFor="let location of locationData.spaceDescriptions"
[value]="location.id">
{{location.name}}
</option>
</ng-container>
</select>
</div>

<div class="mb-4">
<label class="form-label" for="input-location-mention">
How is this information presented in the text?
</label>
<select class="form-select" id="input-location-mention"
[formControl]="form.controls.location.controls.sourceMention">
<option *ngFor="let option of locationSourceMentionOptions"
[value]="option.value">
{{option.label}}
</option>
</select>
</div>

<div class="mb-4">
<label class="form-label" for="input-location-notes">
Additional notes
</label>
<textarea class="form-control" id="input-location-notes"
[formControl]="form.controls.location.controls.note">
</textarea>
</div>
</div>

</ng-container>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AgentDescriptionFormComponent } from './agent-description-form.component';
import { SharedTestingModule } from '@shared/shared-testing.module';
import { DataEntrySharedModule } from '../../shared/data-entry-shared.module';

describe('AgentDescriptionFormComponent', () => {
let component: AgentDescriptionFormComponent;
let fixture: ComponentFixture<AgentDescriptionFormComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [AgentDescriptionFormComponent],
imports: [SharedTestingModule, DataEntrySharedModule,],
});
fixture = TestBed.createComponent(AgentDescriptionFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, FormGroup } from '@angular/forms';
import {
DataEntryAgentDescriptionGQL,
DataEntryAgentDescriptionQuery,
PersonAgentDescriptionGenderGenderChoices as GenderChoices,
PersonAgentDescriptionGenderSourceMentionChoices as GenderSourceMentionChoices,
LocationsInSourceListGQL,
LocationsInSourceListQuery,
PersonAgentDescriptionSourceMentionChoices as LocationSourceMentionChoices,
} from 'generated/graphql';
import { Observable, map, Subject, switchMap, shareReplay, filter } from 'rxjs';
import _ from 'underscore';


@Component({
selector: 'lc-agent-description-form',
templateUrl: './agent-description-form.component.html',
styleUrls: ['./agent-description-form.component.scss'],
})
export class AgentDescriptionFormComponent implements OnChanges, OnDestroy {
@Input() id?: string;

genderOptions: { value: GenderChoices, label: string }[] = [
{ value: GenderChoices.Female, label: 'Female' },
{ value: GenderChoices.Male, label: 'Male' },
{ value: GenderChoices.Other, label: 'Other' },
{ value: GenderChoices.Mixed, label: 'Mixed (for groups)' },
{ value: GenderChoices.Unknown, label: 'Unknown' }
];

genderSourceMentionOptions: { value: GenderSourceMentionChoices, label: string }[] = [
{ value: GenderSourceMentionChoices.Direct, label: 'Mentioned' },
{ value: GenderSourceMentionChoices.Implied, label: 'Implied' },
];

locationSourceMentionOptions: { value: LocationSourceMentionChoices, label: string }[] = [
{ value: LocationSourceMentionChoices.Direct, label: 'Mentioned' },
{ value: LocationSourceMentionChoices.Implied, label: 'Implied' },
];

form = new FormGroup({
designators: new FormControl<string[]>([], { nonNullable: true }),
gender: new FormGroup({
gender: new FormControl<string>(GenderChoices.Unknown),
sourceMention: new FormControl<string>(GenderSourceMentionChoices.Direct),
note: new FormControl<string>(''),
}),
location: new FormGroup({
hasLocation: new FormControl<boolean>(false, { nonNullable: true }),
location: new FormControl<string | null>(null),
sourceMention: new FormControl<string>(LocationSourceMentionChoices.Direct),
note: new FormControl<string>(''),
})
});

isGroup$: Observable<boolean>;
locations$: Observable<LocationsInSourceListQuery>;

private id$ = new Subject<string>();
private data$: Observable<DataEntryAgentDescriptionQuery>;

constructor(
private agentQuery: DataEntryAgentDescriptionGQL,
private locationsQuery: LocationsInSourceListGQL,
) {
this.data$ = this.id$.pipe(
switchMap(id => this.agentQuery.watch({ id }).valueChanges),
map(result => result.data),
takeUntilDestroyed(),
shareReplay(1),
);
this.isGroup$ = this.data$.pipe(
map(result => result.agentDescription?.isGroup || false),
);
this.locations$ = this.data$.pipe(
map(data => data.agentDescription?.source.id),
filter(_.negate(_.isUndefined)),
switchMap(id => this.locationsQuery.watch({ id }).valueChanges),
map(result => result.data),
shareReplay(1),
);
this.data$.subscribe(this.updateFormData.bind(this));
}

ngOnChanges(changes: SimpleChanges): void {
if (changes['id'] && this.id) {
this.id$.next(this.id);
}
}

ngOnDestroy(): void {
this.id$.complete();
}

updateFormData(data: DataEntryAgentDescriptionQuery) {
this.form.setValue({
designators: data.agentDescription?.designators || [],
gender: {
gender: data.agentDescription?.gender?.gender || GenderChoices.Unknown,
sourceMention: data.agentDescription?.gender?.sourceMention || GenderSourceMentionChoices.Direct,
note: data.agentDescription?.gender?.note || '',
},
location: {
hasLocation: data.agentDescription?.location !== null,
location: data.agentDescription?.location?.location.id || null,
sourceMention: data.agentDescription?.location?.sourceMention || LocationSourceMentionChoices.Direct,
note: data.agentDescription?.location?.note || '',
}
});
}

}
Loading

0 comments on commit dd3fb51

Please sign in to comment.