Skip to content

Commit

Permalink
Merge pull request #102 from CentreForDigitalHumanities/feature/sourc…
Browse files Browse the repository at this point in the history
…e-detail

Feature/source detail
  • Loading branch information
XanderVertegaal authored Aug 6, 2024
2 parents ed52588 + 1c2bc3a commit 2d56f8c
Show file tree
Hide file tree
Showing 18 changed files with 356 additions and 15 deletions.
2 changes: 1 addition & 1 deletion backend/person/types/AgentDescriptionType.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ def resolve_describes(
def resolve_person_references(
parent: AgentDescription, info: ResolveInfo
) -> QuerySet[PersonReference]:
return parent.person_references.all() # type: ignore
return PersonReference.objects.filter(person=parent)
11 changes: 10 additions & 1 deletion backend/source/queries.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
from graphene import NonNull, ObjectType
from graphene import ID, Field, NonNull, ObjectType, ResolveInfo
from graphene_django import DjangoListField

from source.models import Source
from source.types.SourceType import SourceType


class SourceQueries(ObjectType):
source = Field(NonNull(SourceType), id=ID(required=True))
sources = DjangoListField(NonNull(SourceType), required=True)

@staticmethod
def resolve_source(root: None, info: ResolveInfo, id: str) -> Source | None:
try:
return SourceType.get_queryset(Source.objects, info).get(pk=id)
except Source.DoesNotExist:
return None
10 changes: 8 additions & 2 deletions backend/source/types/SourceType.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from graphene import Field, Int, ResolveInfo
from graphene import Field, Int, List, NonNull, ResolveInfo
from django.db.models import QuerySet
from graphene_django import DjangoObjectType
from event.models import Episode
from source.models import Source
from event.types.EpisodeType import EpisodeType
from source.types.SourceContentsDateType import SourceContentsDateType
from source.types.SourceWrittenDateType import SourceWrittenDateType


class SourceType(DjangoObjectType):
episodes = List(NonNull(EpisodeType), required=True)
num_of_episodes = Int(required=True)
written_date = Field(SourceWrittenDateType)
contents_date = Field(SourceContentsDateType)
Expand All @@ -29,6 +31,10 @@ def get_queryset(
) -> QuerySet[Source]:
return queryset.all()

@staticmethod
def resolve_episodes(parent: Source, info: ResolveInfo) -> QuerySet[Episode]:
return EpisodeType.get_queryset(Episode.objects, info).filter(source_id=parent.pk)

@staticmethod
def resolve_num_of_episodes(parent: Source, info: ResolveInfo) -> int:
return Episode.objects.filter(source_id=parent.pk).count()
return EpisodeType.get_queryset(Episode.objects, info).filter(source_id=parent.pk).count()
4 changes: 3 additions & 1 deletion frontend/src/app/data-entry/data-entry.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "@shared/shared.module";
import { SourcesComponent } from "./sources/sources.component";
import { SourceComponent } from './source/source.component';
import { EpisodePreviewComponent } from './source/episode-preview/episode-preview.component';

@NgModule({
declarations: [SourcesComponent],
declarations: [SourcesComponent, SourceComponent, EpisodePreviewComponent],
imports: [SharedModule],
exports: [SourcesComponent],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<div class="card mb-4">
<div class="card-header">
<div class="d-flex justify-content-between">
<div>
<lc-icon [icon]="dataIcons.episode" />
&nbsp;
{{ episode.name }}
<br />
<small>
<span>book {{ episode.book }}</span>,
<span>chapter {{ episode.chapter }}</span>
</small>
</div>
<lc-action-button-group
[editLink]="['episode', episode.id]"
(delete)="deleteEpisode(episode.id)"
[showButtonText]="false"
/>
</div>
</div>
<div class="card-body">
<p>{{ episode.summary || "..." }}</p>

<dl>
<dt>Agents</dt>
<dd
*ngIf="episode.agents.length > 0; else noAgents"
class="ms-4"
>
<ul class="inline-list">
<li
*ngFor="let agent of episode.agents"
class="inline-list-item"
>
<a
[routerLink]="['agent/', agent.id]"
class="icon-link"
>
<lc-icon [icon]="agentIcon(agent)" />
{{ agent.name }}
</a>
</li>
</ul>
</dd>
<ng-template #noAgents>
<dd class="ms-4">No agents have been added yet.</dd>
</ng-template>

<dt>Locations</dt>
<dd
*ngIf="episode.spaces.length > 0; else noSpaces"
class="ms-4"
>
<ul class="inline-list">
<li
*ngFor="let space of episode.spaces"
class="inline-list-item"
>
<a
[routerLink]="['location/', space.id]"
class="icon-link"
>
<lc-icon [icon]="dataIcons.location" />
{{ space.name }}
</a>
</li>
</ul>
</dd>
<ng-template #noSpaces>
<dd class="ms-4">No locations have been added yet.</dd>
</ng-template>

<dt>Objects</dt>
<dd
*ngIf="
episode.gifts.length > 0 || episode.letters.length > 0;
else noObjects
"
class="ms-4"
>
<ul class="inline-list">
<li
*ngFor="let gift of episode.gifts"
class="inline-list-item"
>
<a [routerLink]="['gift/', gift.id]" class="icon-link">
<lc-icon [icon]="dataIcons.gift" />
{{ gift.name }}
</a>
</li>
<li
*ngFor="let letter of episode.letters"
class="inline-list-item"
>
<a
[routerLink]="['letter/', letter.id]"
class="icon-link"
>
<lc-icon [icon]="dataIcons.letter" />
{{ letter.name }}
</a>
</li>
</ul>
</dd>
<ng-template #noObjects>
<dd class="ms-4">No objects have been added yet.</dd>
</ng-template>
</dl>
</div>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";

import { EpisodePreviewComponent } from "./episode-preview.component";

describe("EpisodePreviewComponent", () => {
let component: EpisodePreviewComponent;
let fixture: ComponentFixture<EpisodePreviewComponent>;

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

it("should create", () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Component, Input } from "@angular/core";
import { dataIcons } from "@shared/icons";
import { DataEntrySourceDetailQuery } from "generated/graphql";

type QueriedEpisode = NonNullable<DataEntrySourceDetailQuery["source"]["episodes"]>[0];

@Component({
selector: "lc-episode-preview",
templateUrl: "./episode-preview.component.html",
styleUrls: ["./episode-preview.component.scss"],
})
export class EpisodePreviewComponent {
@Input({ required: true })
public episode!: QueriedEpisode;
public dataIcons = dataIcons;

public deleteEpisode(episodeId: string): void {
console.log("Deleting episode with id", episodeId);
}

public agentIcon(agent: QueriedEpisode["agents"][0]): string {
if (agent.isGroup) {
return dataIcons.group;
}
if (agent.describes?.some(person => person?.identifiable)) {
return dataIcons.person;
}
return dataIcons.personUnknown;
}
}
26 changes: 26 additions & 0 deletions frontend/src/app/data-entry/source/source.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<lc-breadcrumb [breadcrumbs]="breadcrumbs()" />

<h1 class="mb-4">
<lc-icon [icon]="dataIcons.source" />
<span *ngIf="source$ | async as source" class="ms-2">
{{ source.medievalAuthor }}, <i>{{ source.medievalTitle }}</i>
</span>
</h1>

<h2 class="mb-4">Episodes</h2>

<p>
Identify <i>episodes</i> in the narrative that relate to epistolary
communication. For each episode, fill in what happens and what agents,
objects, and locations are involved.
</p>

<div class="mb-4" *ngIf="source$ | async as source">
<lc-episode-preview *ngFor="let episode of source.episodes" [episode]="episode" />
</div>
<div class="btn-group mb-4">
<button class="btn btn-primary" disabled>
<lc-icon [icon]="actionIcons.create" />
Add a new episode
</button>
</div>
Empty file.
23 changes: 23 additions & 0 deletions frontend/src/app/data-entry/source/source.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";

import { SourceComponent } from "./source.component";
import { SharedTestingModule } from "@shared/shared-testing.module";

describe("SourceComponent", () => {
let component: SourceComponent;
let fixture: ComponentFixture<SourceComponent>;

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

it("should create", () => {
expect(component).toBeTruthy();
});
});
50 changes: 50 additions & 0 deletions frontend/src/app/data-entry/source/source.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Component, computed } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from "@angular/router";
import { actionIcons, dataIcons } from "@shared/icons";
import { DataEntrySourceDetailGQL } from "generated/graphql";
import { map, shareReplay, switchMap } from "rxjs";

@Component({
selector: "lc-source",
templateUrl: "./source.component.html",
styleUrls: ["./source.component.scss"],
})
export class SourceComponent {
public breadcrumbs = computed(() => [
{
label: "Lettercraft",
link: "/",
},
{
label: "Data entry",
link: "/data-entry",
},
{
label: this.sourceTitle(),
link: "/data-entry/source/" + this.route.snapshot.params["id"],
},
]);

public source$ = this.route.params.pipe(
map((params) => params["id"]),
switchMap((id) => this.sourceDetailQuery.watch({ id }).valueChanges),
map((result) => result.data.source),
shareReplay(1)
);

public sourceTitle = toSignal(
this.source$.pipe(
map((source) => `${source.medievalAuthor}, ${source.medievalTitle}`)
),
{ initialValue: "" }
);

public dataIcons = dataIcons;
public actionIcons = actionIcons;

constructor(
private route: ActivatedRoute,
private sourceDetailQuery: DataEntrySourceDetailGQL
) {}
}
41 changes: 41 additions & 0 deletions frontend/src/app/data-entry/source/source.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
query DataEntrySourceDetail($id: ID!) {
source(id: $id) {
id
name
editionAuthor
editionTitle
medievalAuthor
medievalTitle
numOfEpisodes
episodes {
id
name
description
summary
book
chapter
page
agents {
id
name
isGroup
describes {
id
identifiable
}
}
gifts {
id
name
}
letters {
id
name
}
spaces {
id
name
}
}
}
}
2 changes: 1 addition & 1 deletion frontend/src/app/data-entry/sources/sources.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h1>Data entry</h1>
<li *ngFor="let source of sources$ | async; trackBy: identify"
class="list-group-item list-group-item-action">
<div class="d-flex align-items-center">
<a [routerLink]="['source', source.id]" class="mb-1 h5 stretched-link">{{ source.name }}</a>
<a [routerLink]="[source.id]" class="mb-1 h5 stretched-link">{{ source.name }}</a>
<small class="text-body-secondary ms-2">
({source.numOfEpisodes, plural, =0 {No episodes} =1 {1 episode} other {{{ source.numOfEpisodes}} episodes}})
</small>
Expand Down
Loading

0 comments on commit 2d56f8c

Please sign in to comment.