Skip to content

Commit

Permalink
SF-3077 Add UI for mixed training sources
Browse files Browse the repository at this point in the history
  • Loading branch information
RaymondLuong3 committed Nov 28, 2024
1 parent 8bed61c commit 1ac6820
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<ng-container *transloco="let t; read: 'book_select'">
@if (availableBooks.length > 0 && !readonly) {
@if (availableBooks.length > 0 && !readonly && !basicMode) {
<div>
<mat-checkbox
class="ot-checkbox"
Expand Down Expand Up @@ -44,14 +44,20 @@
[disabled]="readonly"
(change)="onChipListChange(book)"
>
<mat-chip-option
[value]="book"
[selected]="book.selected"
[matTooltip]="t('book_progress', { percent: getPercentage(book) | l10nPercent })"
>
{{ "canon.book_names." + book.bookId | transloco }}
<div class="border-fill" [style.width]="book.progressPercentage + '%'"></div>
</mat-chip-option>
@if (!basicMode) {
<mat-chip-option
[value]="book"
[selected]="book.selected"
[matTooltip]="t('book_progress', { percent: getPercentage(book) | l10nPercent })"
>
{{ "canon.book_names." + book.bookId | transloco }}
<div class="border-fill" [style.width]="book.progressPercentage + '%'"></div>
</mat-chip-option>
} @else {
<mat-chip-option [value]="book" [selected]="book.selected">
{{ "canon.book_names." + book.bookId | transloco }}
</mat-chip-option>
}
</mat-chip-listbox>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class BookMultiSelectComponent extends SubscriptionDisposable implements
@Input() availableBooks: number[] = [];
@Input() selectedBooks: number[] = [];
@Input() readonly: boolean = false;
@Input() basicMode: boolean = false;
@Output() bookSelect = new EventEmitter<number[]>();

protected loaded = false;
Expand Down
3 changes: 2 additions & 1 deletion src/SIL.XForge.Scripture/ClientApp/src/app/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SFProjectProfileDoc } from '../core/models/sf-project-profile-doc';
import { roleCanAccessCommunityChecking, roleCanAccessTranslate } from '../core/models/sf-project-role-info';
import { SFProjectUserConfigDoc } from '../core/models/sf-project-user-config-doc';
import { SelectableProject } from '../core/paratext.service';
import { DraftSource } from '../translate/draft-generation/draft-sources.service';

// Regular expression for getting the verse from a segment ref
// Some projects will have the right to left marker in the segment attribute which we need to account for
Expand Down Expand Up @@ -190,7 +191,7 @@ export function checkAppAccess(
}
}

export function projectLabel(project: SelectableProject | undefined): string {
export function projectLabel(project: SelectableProject | DraftSource | undefined): string {
if (project == null || (!project.shortName && !project.name)) {
return '';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ <h4 class="explanation">
</button>
</div>
</mat-step>
<mat-step [completed]="isTrainingOptional || userSelectedTrainingBooks.length > 0">
<mat-step [completed]="isTrainingOptional || userSelectedSourceTrainingBooks.length > 0">
<ng-template matStepLabel>
{{ t("choose_books_for_training_label") }}
</ng-template>
<h1 class="mat-headline-4">{{ t("choose_books_for_training") }}</h1>
<h2>Translated Books</h2>
<p>{{ targetProjectName }}</p>
<app-book-multi-select
[availableBooks]="availableTrainingBooks"
[selectedBooks]="initialSelectedTrainingBooks"
Expand Down Expand Up @@ -103,10 +105,34 @@ <h4 class="explanation">
</div>
</app-notice>
}
@if (unusableTranslateTargetBooks.length) {
<app-notice>
<transloco key="draft_generation_steps.unusable_target_books"></transloco>
</app-notice>
<h2>Reference Books</h2>
<p>{{ trainingSourceProjectName }}</p>
@if (selectableTrainingBooks.length === 0) {
<app-notice mode="basic" type="light"
>Training books will appear as you select books under Translated Books</app-notice
>
} @else {
<app-book-multi-select
[availableBooks]="selectableTrainingBooks"
[selectedBooks]="userSelectedSourceTrainingBooks"
[basicMode]="true"
(bookSelect)="onSourceTrainingBookSelect($event)"
></app-book-multi-select>
}
@if (trainingAdditionalSourceProjectName != null) {
<p>{{ trainingAdditionalSourceProjectName }}</p>
@if (selectableAdditionalTrainingBooks.length === 0) {
<app-notice mode="basic" type="light"
>Training books will appear as you select books under Translated Books</app-notice
>
} @else {
<app-book-multi-select
[availableBooks]="selectableAdditionalTrainingBooks"
[selectedBooks]="userSelectedAdditionalSourceTrainingBooks"
[basicMode]="true"
(bookSelect)="onAdditionalSourceTrainingBookSelect($event)"
></app-book-multi-select>
}
}
@if (showBookSelectionError) {
<app-notice type="error">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import { filterNullish } from 'xforge-common/util/rxjs-util';
import { TrainingDataDoc } from '../../../core/models/training-data-doc';
import { BookMultiSelectComponent } from '../../../shared/book-multi-select/book-multi-select.component';
import { SharedModule } from '../../../shared/shared.module';
import { projectLabel } from '../../../shared/utils';
import { NllbLanguageService } from '../../nllb-language.service';
import { ProjectScriptureRange } from '../draft-generation';
import { DraftSource, DraftSourcesService } from '../draft-sources.service';
import { DraftSource, DraftSourceIds, DraftSourcesService } from '../draft-sources.service';
import { TrainingDataMultiSelectComponent } from '../training-data/training-data-multi-select.component';
import { TrainingDataUploadDialogComponent } from '../training-data/training-data-upload-dialog.component';
import { TrainingDataService } from '../training-data/training-data.service';
Expand Down Expand Up @@ -56,6 +57,9 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem

availableTranslateBooks?: number[] = undefined;
availableTrainingBooks: number[] = [];
selectableTrainingBooks: number[] = [];
selectableAdditionalTrainingBooks: number[] = [];
private availableAdditionalTrainingBooks: number[] = [];
availableTrainingData: Readonly<TrainingData>[] = [];

// Unusable books do not exist in the target or corresponding drafting/training source project
Expand All @@ -68,14 +72,17 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
initialSelectedTranslateBooks: number[] = [];
userSelectedTrainingBooks: number[] = [];
userSelectedTranslateBooks: number[] = [];
userSelectedSourceTrainingBooks: number[] = [];
userSelectedAdditionalSourceTrainingBooks: number[] = [];

selectedTrainingDataIds: string[] = [];

// When translate books are selected, they will be filtered out from this list
initialAvailableTrainingBooks: number[] = [];
private initialAvailableTrainingBooks: number[] = [];

draftingSourceProjectName?: string;
trainingSourceProjectName?: string;
trainingAdditionalSourceProjectName?: string;
targetProjectName?: string;

showBookSelectionError = false;
Expand All @@ -88,6 +95,7 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
expandUnusableTrainingBooks = false;
isStepsCompleted = false;

private draftSourceProjectIds?: DraftSourceIds;
private trainingDataQuery?: RealtimeQuery<TrainingDataDoc>;
private trainingDataSub?: Subscription;

Expand All @@ -105,13 +113,25 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
ngOnInit(): void {
this.subscribe(
this.draftSourcesService.getDraftProjectSources().pipe(
filter(({ target, source, alternateSource, alternateTrainingSource }) => {
this.setProjectDisplayNames(target, alternateSource ?? source, alternateTrainingSource);
filter(({ target, source, alternateSource, alternateTrainingSource, additionalTrainingSource }) => {
this.setProjectDisplayNames(
target,
alternateSource ?? source,
alternateTrainingSource,
additionalTrainingSource
);
return target != null && source != null;
})
),
// Build book lists
async ({ target, source, alternateSource, alternateTrainingSource }) => {
async ({
target,
source,
alternateSource,
alternateTrainingSource,
additionalTrainingSource,
draftSourceIds
}) => {
// The null values will have been filtered above
target = target!;
// Use the alternate source if specified, otherwise use the source
Expand All @@ -123,21 +143,20 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
(await this.nllbLanguageService.isNllbLanguageAsync(draftingSource.writingSystem.tag));

const draftingSourceBooks = new Set<number>();
let trainingSourceBooks = new Set<number>();

for (const text of draftingSource.texts) {
draftingSourceBooks.add(text.bookNum);
}

if (alternateTrainingSource != null) {
for (const text of alternateTrainingSource.texts) {
trainingSourceBooks.add(text.bookNum);
}
} else {
// If no training source project, use drafting source project books
trainingSourceBooks = draftingSourceBooks;
}
let trainingSourceBooks: Set<number> =
alternateTrainingSource != null
? new Set<number>(alternateTrainingSource.texts.map(t => t.bookNum))
: draftingSourceBooks;
let additionalTrainingSourceBooks: Set<number> | undefined =
additionalTrainingSource != null
? new Set<number>(additionalTrainingSource?.texts.map(t => t.bookNum))
: undefined;

this.draftSourceProjectIds = draftSourceIds;
this.availableTranslateBooks = [];

// If book exists in both target and source, add to available books.
Expand Down Expand Up @@ -166,6 +185,9 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
} else {
this.unusableTrainingSourceBooks.push(bookNum);
}
if (additionalTrainingSourceBooks != null && additionalTrainingSourceBooks.has(bookNum)) {
this.availableAdditionalTrainingBooks.push(bookNum);
}
}

// Store initially available training books that will be filtered to remove user selected translate books
Expand All @@ -192,7 +214,6 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
// Query for all training data files in the project
this.trainingDataQuery?.dispose();
this.trainingDataQuery = await this.trainingDataService.queryTrainingDataAsync(projectDoc.id);

let projectChanged: boolean = true;

// Subscribe to this query, and show these
Expand Down Expand Up @@ -223,7 +244,19 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
}

onTrainingBookSelect(selectedBooks: number[]): void {
const newBookSelections: number[] = selectedBooks.filter(b => !this.userSelectedTrainingBooks.includes(b));
this.userSelectedTrainingBooks = selectedBooks;
this.selectableTrainingBooks = selectedBooks;
this.selectableAdditionalTrainingBooks = this.availableAdditionalTrainingBooks.filter(b =>
selectedBooks.includes(b)
);
for (const bookNum of newBookSelections) {
this.userSelectedSourceTrainingBooks.push(bookNum);
if (this.selectableAdditionalTrainingBooks.includes(bookNum)) {
this.userSelectedAdditionalSourceTrainingBooks.push(bookNum);
}
}

this.clearErrorMessage();
}

Expand All @@ -232,6 +265,16 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
this.clearErrorMessage();
}

onSourceTrainingBookSelect(selectedBooks: number[]): void {
this.userSelectedSourceTrainingBooks = selectedBooks;
this.clearErrorMessage();
}

onAdditionalSourceTrainingBookSelect(selectedBooks: number[]): void {
this.userSelectedAdditionalSourceTrainingBooks = selectedBooks;
this.clearErrorMessage();
}

onTranslateBookSelect(selectedBooks: number[]): void {
this.userSelectedTranslateBooks = selectedBooks;
this.clearErrorMessage();
Expand All @@ -251,20 +294,41 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
this.stepper.next();
} else {
this.isStepsCompleted = true;
const trainingScriptureRange: ProjectScriptureRange = this.convertToScriptureRange(
this.draftSourceProjectIds!.trainingAlternateSourceId ?? this.draftSourceProjectIds!.trainingSourceId,
this.userSelectedSourceTrainingBooks
);

const trainingScriptureRanges: ProjectScriptureRange[] = [trainingScriptureRange];
// Use the additional training range if selected
const useAdditionalTranslateRange: boolean = this.userSelectedAdditionalSourceTrainingBooks.length > 0;
if (useAdditionalTranslateRange) {
trainingScriptureRanges.push(
this.convertToScriptureRange(
this.draftSourceProjectIds!.trainingAdditionalSourceId,
this.userSelectedAdditionalSourceTrainingBooks
)
);
}
const translationScriptureRange: ProjectScriptureRange = this.convertToScriptureRange(
this.draftSourceProjectIds!.draftingSourceId,
this.userSelectedTranslateBooks
);
this.done.emit({
// TODO: Can trainingBooks and translationBooks be removed?
trainingBooks: this.userSelectedTrainingBooks,
trainingScriptureRanges: [],
trainingScriptureRanges,
trainingDataFiles: this.selectedTrainingDataIds,
translationBooks: this.userSelectedTranslateBooks,
translationScriptureRanges: [],
translationScriptureRanges: [translationScriptureRange],
fastTraining: this.fastTraining
});
}
}

/**
* Filter selected translate books from available/selected training books.
* Currently, training books cannot in the set of translate books,
* Currently, training books cannot be in the set of translate books,
* but this requirement may be removed in the future.
*/
updateTrainingBooks(): void {
Expand All @@ -279,13 +343,21 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
);

this.initialSelectedTrainingBooks = newSelectedTrainingBooks;
this.userSelectedTrainingBooks = newSelectedTrainingBooks;
this.userSelectedTrainingBooks = [...newSelectedTrainingBooks];
this.selectableTrainingBooks = [...newSelectedTrainingBooks];
this.selectableAdditionalTrainingBooks = this.availableAdditionalTrainingBooks.filter(b =>
newSelectedTrainingBooks.includes(b)
);
}

bookNames(books: number[]): string {
return books.map(bookNum => this.i18n.localizeBook(bookNum)).join(', ');
}

private convertToScriptureRange(projectId: string, books: number[]): ProjectScriptureRange {
return { projectId: projectId, scriptureRange: books.map(b => Canon.bookNumberToId(b)).join(';') };
}

private validateCurrentStep(): boolean {
const isValid = this.stepper.selected?.completed!;
this.showBookSelectionError = !isValid;
Expand All @@ -307,7 +379,7 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem

// Set the selected books to the intersection, or if the intersection is empty, do not select any
this.initialSelectedTranslateBooks = intersection.length > 0 ? intersection : [];
this.userSelectedTranslateBooks = this.initialSelectedTranslateBooks;
this.userSelectedTranslateBooks = [...this.initialSelectedTranslateBooks];
}

private setInitialTrainingBooks(availableBooks: number[]): void {
Expand All @@ -321,7 +393,11 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem

// Set the selected books to the intersection, or if the intersection is empty, do not select any
this.initialSelectedTrainingBooks = intersection.length > 0 ? intersection : [];
this.userSelectedTrainingBooks = this.initialSelectedTrainingBooks;
this.userSelectedTrainingBooks = [...this.initialSelectedTrainingBooks];
this.userSelectedSourceTrainingBooks = [...this.initialSelectedTrainingBooks];
this.userSelectedAdditionalSourceTrainingBooks = this.availableAdditionalTrainingBooks.filter(b =>
this.initialSelectedTrainingBooks.includes(b)
);
}

private setInitialTrainingDataFiles(availableDataFiles: string[]): void {
Expand All @@ -342,12 +418,14 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
private setProjectDisplayNames(
target: DraftSource | undefined,
draftingSource: DraftSource | undefined,
trainingSource: DraftSource | undefined
trainingSource: DraftSource | undefined,
additionalTrainingSource: DraftSource | undefined
): void {
this.targetProjectName = target != null ? `${target.shortName} - ${target.name}` : '';
this.draftingSourceProjectName =
draftingSource != null ? `${draftingSource.shortName} - ${draftingSource.name}` : '';
this.targetProjectName = target != null ? projectLabel(target) : '';
this.draftingSourceProjectName = draftingSource != null ? projectLabel(draftingSource) : '';
this.trainingSourceProjectName =
trainingSource != null ? `${trainingSource.shortName} - ${trainingSource.name}` : this.draftingSourceProjectName;
trainingSource != null ? projectLabel(trainingSource) : this.draftingSourceProjectName;
this.trainingAdditionalSourceProjectName =
additionalTrainingSource != null ? projectLabel(additionalTrainingSource) : '';
}
}
Loading

0 comments on commit 1ac6820

Please sign in to comment.