Skip to content

Commit ab5f937

Browse files
committed
SF-3528 Require users to select formatting options the first time
1 parent b8edfe0 commit ab5f937

File tree

5 files changed

+175
-86
lines changed

5 files changed

+175
-86
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.html

Lines changed: 98 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -21,103 +21,117 @@
2121
</mat-panel-description>
2222
</mat-expansion-panel-header>
2323
<div class="draft-entry-body">
24-
@if (canDownloadBuild) {
25-
<p>{{ t("click_book_to_preview") }}</p>
26-
<p class="book-buttons">
27-
<app-draft-preview-books [build]="entry" />
24+
@if (requireSelectingFormattingOptions) {
25+
<p class="require-formatting-options">
26+
{{ t("select_formatting_options") }}
2827
</p>
29-
<div class="draft-options">
30-
<app-draft-download-button [build]="entry" [flat]="true" />
31-
@if (featureFlags.usfmFormat.enabled && isLatestBuild) {
32-
<button mat-button class="format-usfm" [routerLink]="['format']">
33-
<mat-icon>build</mat-icon> {{ t("formatting_options") }}
34-
</button>
35-
}
28+
<div class="formatting-options-container">
29+
<button mat-flat-button class="format-usfm" [routerLink]="['format']">
30+
<mat-icon>build</mat-icon>
31+
<div class="hide-lt-lg">{{ t("formatting_options") }}</div>
32+
<div class="hide-gt-lg">{{ t("options") }}</div>
33+
</button>
34+
<span>{{ t("new") }} {{ t("change_line_breaks_and_quotation_marks") }}</span>
3635
</div>
37-
}
38-
@if (buildFaulted) {
39-
<p>
40-
<strong>{{ t("error_details") }}</strong>
41-
</p>
42-
<table mat-table [dataSource]="buildFaultDetails">
43-
<ng-container matColumnDef="heading">
44-
<td mat-cell *matCellDef="let element">{{ element.heading }}</td>
45-
</ng-container>
46-
<ng-container matColumnDef="details">
47-
<td mat-cell *matCellDef="let element">{{ element.details }}</td>
48-
</ng-container>
49-
<tr mat-row *matRowDef="let row; columns: ['heading', 'details']"></tr>
50-
</table>
51-
}
52-
@if (buildRequestedAtDate != null && buildRequestedByUserName != null) {
53-
<p class="requested-label">
54-
{{
55-
t("requested_by", {
56-
requestedAtTime: buildRequestedAtDate,
57-
requestedByUserName: buildRequestedByUserName
58-
})
59-
}}
60-
</p>
61-
} @else if (buildRequestedAtDate != null) {
62-
<p class="requested-label">
63-
{{ t("requested_at", { requestedAtTime: buildRequestedAtDate }) }}
64-
</p>
65-
}
66-
@if (hasTrainingConfiguration || hasTrainingDataFiles) {
36+
} @else {
6737
@if (canDownloadBuild) {
68-
<p>
69-
<a
70-
href="javascript:;"
71-
(click)="trainingConfigurationOpen = !trainingConfigurationOpen"
72-
class="training-configuration-link"
73-
>
74-
<mat-icon>expand_{{ trainingConfigurationOpen ? "less" : "more" }}</mat-icon>
75-
{{
76-
t(
77-
trainingConfigurationOpen
78-
? "hide_model_training_configuration"
79-
: "show_model_training_configuration"
80-
)
81-
}}
82-
</a>
38+
<p>{{ t("click_book_to_preview") }}</p>
39+
<p class="book-buttons">
40+
<app-draft-preview-books [build]="entry" />
8341
</p>
42+
<div class="draft-options">
43+
<app-draft-download-button [build]="entry" [flat]="true" />
44+
@if (featureFlags.usfmFormat.enabled && isLatestBuild) {
45+
<button mat-button class="format-usfm" [routerLink]="['format']">
46+
<mat-icon>build</mat-icon> {{ t("formatting_options") }}
47+
</button>
48+
}
49+
</div>
8450
}
85-
@if (trainingConfigurationOpen && hasTrainingConfiguration) {
51+
@if (buildFaulted) {
8652
<p>
87-
<strong>{{ t("training_model_description") }}</strong>
53+
<strong>{{ t("error_details") }}</strong>
8854
</p>
89-
<table mat-table [dataSource]="trainingConfiguration">
90-
<ng-container matColumnDef="scriptureRange">
91-
<th mat-header-cell *matHeaderCellDef>{{ t("training_books") }}</th>
92-
<td mat-cell *matCellDef="let element">{{ element.scriptureRange }}</td>
93-
</ng-container>
94-
<ng-container matColumnDef="source">
95-
<th mat-header-cell *matHeaderCellDef>
96-
{{ i18n.getLanguageDisplayName(sourceLanguage) }}
97-
</th>
98-
<td mat-cell *matCellDef="let element">{{ element.source }}</td>
55+
<table mat-table [dataSource]="buildFaultDetails">
56+
<ng-container matColumnDef="heading">
57+
<td mat-cell *matCellDef="let element">{{ element.heading }}</td>
9958
</ng-container>
100-
<ng-container matColumnDef="target">
101-
<th mat-header-cell *matHeaderCellDef>
102-
{{ i18n.getLanguageDisplayName(targetLanguage) }}
103-
</th>
104-
<td mat-cell *matCellDef="let element">{{ element.target }}</td>
59+
<ng-container matColumnDef="details">
60+
<td mat-cell *matCellDef="let element">{{ element.details }}</td>
10561
</ng-container>
106-
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
107-
<tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
62+
<tr mat-row *matRowDef="let row; columns: ['heading', 'details']"></tr>
10863
</table>
10964
}
110-
@if (trainingConfigurationOpen && hasTrainingDataFiles) {
111-
<p>
112-
<span>{{ t("training_data_files_used") }}</span>
113-
<br />
114-
<span class="training-files">{{ trainingFilesString(trainingDataFiles) }}</span>
65+
@if (buildRequestedAtDate != null && buildRequestedByUserName != null) {
66+
<p class="requested-label">
67+
{{
68+
t("requested_by", {
69+
requestedAtTime: buildRequestedAtDate,
70+
requestedByUserName: buildRequestedByUserName
71+
})
72+
}}
73+
</p>
74+
} @else if (buildRequestedAtDate != null) {
75+
<p class="requested-label">
76+
{{ t("requested_at", { requestedAtTime: buildRequestedAtDate }) }}
77+
</p>
78+
}
79+
@if (hasTrainingConfiguration || hasTrainingDataFiles) {
80+
@if (canDownloadBuild) {
81+
<p>
82+
<a
83+
href="javascript:;"
84+
(click)="trainingConfigurationOpen = !trainingConfigurationOpen"
85+
class="training-configuration-link"
86+
>
87+
<mat-icon>expand_{{ trainingConfigurationOpen ? "less" : "more" }}</mat-icon>
88+
{{
89+
t(
90+
trainingConfigurationOpen
91+
? "hide_model_training_configuration"
92+
: "show_model_training_configuration"
93+
)
94+
}}
95+
</a>
96+
</p>
97+
}
98+
@if (trainingConfigurationOpen && hasTrainingConfiguration) {
99+
<p>
100+
<strong>{{ t("training_model_description") }}</strong>
101+
</p>
102+
<table mat-table [dataSource]="trainingConfiguration">
103+
<ng-container matColumnDef="scriptureRange">
104+
<th mat-header-cell *matHeaderCellDef>{{ t("training_books") }}</th>
105+
<td mat-cell *matCellDef="let element">{{ element.scriptureRange }}</td>
106+
</ng-container>
107+
<ng-container matColumnDef="source">
108+
<th mat-header-cell *matHeaderCellDef>
109+
{{ i18n.getLanguageDisplayName(sourceLanguage) }}
110+
</th>
111+
<td mat-cell *matCellDef="let element">{{ element.source }}</td>
112+
</ng-container>
113+
<ng-container matColumnDef="target">
114+
<th mat-header-cell *matHeaderCellDef>
115+
{{ i18n.getLanguageDisplayName(targetLanguage) }}
116+
</th>
117+
<td mat-cell *matCellDef="let element">{{ element.target }}</td>
118+
</ng-container>
119+
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
120+
<tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
121+
</table>
122+
}
123+
@if (trainingConfigurationOpen && hasTrainingDataFiles) {
124+
<p>
125+
<span>{{ t("training_data_files_used") }}</span>
126+
<br />
127+
<span class="training-files">{{ trainingFilesString(trainingDataFiles) }}</span>
128+
</p>
129+
}
130+
} @else {
131+
<p class="no-training-configuration">
132+
{{ t("training_data_not_configured") }}
115133
</p>
116134
}
117-
} @else {
118-
<p class="no-training-configuration">
119-
{{ t("training_data_not_configured") }}
120-
</p>
121135
}
122136
</div>
123137
</mat-expansion-panel>

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ mat-panel-description {
2727
column-gap: 8px;
2828
}
2929

30+
.formatting-options-container {
31+
display: flex;
32+
column-gap: 16px;
33+
align-items: center;
34+
35+
::ng-deep mat-icon {
36+
display: flex;
37+
flex-shrink: 0;
38+
}
39+
}
40+
3041
.subtitle,
3142
.training-files {
3243
font-size: 0.9em;

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.spec.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
22
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
33
import { RouterModule } from '@angular/router';
44
import { createTestUserProfile } from 'realtime-server/lib/esm/common/models/user-test-data';
5+
import { SFProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project';
56
import { createTestProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-test-data';
7+
import {
8+
DraftConfig,
9+
ParagraphBreakFormat,
10+
QuoteFormat,
11+
TranslateConfig
12+
} from 'realtime-server/lib/esm/scriptureforge/models/translate-config';
613
import { of } from 'rxjs';
714
import { anything, instance, mock, when } from 'ts-mockito';
815
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
@@ -31,7 +38,7 @@ const mockedTrainingDataService = mock(TrainingDataService);
3138
const mockedActivatedProjectService = mock(ActivatedProjectService);
3239
const mockedFeatureFlagsService = mock(FeatureFlagService);
3340

34-
describe('DraftHistoryEntryComponent', () => {
41+
fdescribe('DraftHistoryEntryComponent', () => {
3542
let component: DraftHistoryEntryComponent;
3643
let fixture: ComponentFixture<DraftHistoryEntryComponent>;
3744

@@ -313,6 +320,34 @@ describe('DraftHistoryEntryComponent', () => {
313320
});
314321
});
315322

323+
describe('setDraftFormat', () => {
324+
it('should show set draft format UI', fakeAsync(() => {
325+
when(mockedActivatedProjectService.projectDoc).thenReturn(getProjectProfileDoc());
326+
component.entry = { id: 'build01', state: BuildStates.Completed, message: 'Completed' } as BuildDto;
327+
component.isLatestBuild = true;
328+
tick();
329+
fixture.detectChanges();
330+
expect(fixture.nativeElement.querySelector('.require-formatting-options')).not.toBeNull();
331+
}));
332+
333+
it('should hide draft format UI', fakeAsync(() => {
334+
when(mockedActivatedProjectService.projectDoc).thenReturn(
335+
getProjectProfileDoc({
336+
translateConfig: {
337+
draftConfig: {
338+
usfmConfig: { paragraphFormat: ParagraphBreakFormat.BestGuess, quoteFormat: QuoteFormat.Denormalized }
339+
} as DraftConfig
340+
} as TranslateConfig
341+
})
342+
);
343+
component.entry = { id: 'build01', state: BuildStates.Completed, message: 'Completed' } as BuildDto;
344+
component.isLatestBuild = true;
345+
tick();
346+
fixture.detectChanges();
347+
expect(fixture.nativeElement.querySelector('.require-formatting-options')).toBeNull();
348+
}));
349+
});
350+
316351
describe('formatDate', () => {
317352
it('should handle undefined values', () => {
318353
expect(component.formatDate(undefined)).toBe('');
@@ -384,4 +419,12 @@ describe('DraftHistoryEntryComponent', () => {
384419

385420
return entry;
386421
}
422+
423+
function getProjectProfileDoc(args: Partial<SFProjectProfile> = {}): SFProjectProfileDoc {
424+
const targetProjectDoc = {
425+
id: 'project01',
426+
data: createTestProjectProfile({ ...args })
427+
} as SFProjectProfileDoc;
428+
return targetProjectDoc;
429+
}
387430
});

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,24 @@ export class DraftHistoryEntryComponent {
270270
return this.i18n.enumerateList(this._translationSources) + ' \u2022'; // &bull; •
271271
}
272272

273-
@Input() isLatestBuild = false;
273+
private _requireSelectingFormattingOptions?: boolean;
274+
get requireSelectingFormattingOptions(): boolean {
275+
return this._requireSelectingFormattingOptions ?? false;
276+
}
277+
278+
private _isLatestBuild: boolean = false;
279+
@Input() set isLatestBuild(value: boolean) {
280+
this._isLatestBuild = value;
281+
if (this._requireSelectingFormattingOptions != null) return;
282+
if (this.activatedProjectService.projectDoc != null) {
283+
this._requireSelectingFormattingOptions =
284+
this.activatedProjectService.projectDoc.data?.translateConfig.draftConfig.usfmConfig == null;
285+
}
286+
}
287+
288+
get isLatestBuild(): boolean {
289+
return this._isLatestBuild;
290+
}
274291

275292
trainingConfigurationOpen = false;
276293

src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@
274274
"use_echo": "Use Echo Translation Engine"
275275
},
276276
"draft_history_entry": {
277+
"change_line_breaks_and_quotation_marks": "You can change how line breaks and quotation marks are handled so the draft is easier to edit.",
277278
"click_book_to_preview": "Click a book below to preview the draft and add it to your project.",
278279
"download_zip": "Download draft as ZIP file",
279280
"draft_active": "Running",
@@ -286,8 +287,11 @@
286287
"finished_at": "Finished {{ finishedAtTime }}",
287288
"formatting_options": "Formatting options",
288289
"hide_model_training_configuration": "Hide model training configuration",
290+
"new": "NEW!",
291+
"options": "Options",
289292
"requested_at": "Requested {{ requestedAtTime }}.",
290293
"requested_by": "Requested by {{ requestedByUserName }} at {{ requestedAtTime }}.",
294+
"select_formatting_options": "The draft has been created, and there are new formatting options to select. You will only need to do this once, but you can make changes for every new draft if you wish.",
291295
"show_model_training_configuration": "Show model training configuration",
292296
"training_books": "Training books",
293297
"training_data_files_used": "The following data files were used to train the model.",

0 commit comments

Comments
 (0)