Skip to content

Commit

Permalink
feat: implement unit detail page
Browse files Browse the repository at this point in the history
  • Loading branch information
kyubisation committed Jan 5, 2020
1 parent 0a4d63b commit 9d1b8a5
Show file tree
Hide file tree
Showing 16 changed files with 317 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
browserslist
LICENSE
coverage
app
app/*
package-lock.json
.*
*.ico
Expand Down
7 changes: 7 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"scripts": []
},
"configurations": {
"de": {
"i18nLocale": "de",
"i18nFile": "src/locale/xlf2/messages.de.xlf"
},
"production": {
"fileReplacements": [
{
Expand Down Expand Up @@ -67,6 +71,9 @@
"browserTarget": "angular-t9n:build"
},
"configurations": {
"de": {
"browserTarget": "angular-t9n:build:de"
},
"production": {
"browserTarget": "angular-t9n:build:production"
}
Expand Down
52 changes: 51 additions & 1 deletion src/app/target/core/translation-target.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { SortDirection } from '@angular/material/sort';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { filter, first, map, switchMap } from 'rxjs/operators';
import {
debounceTime,
distinctUntilChanged,
filter,
first,
map,
skip,
startWith,
switchMap,
takeUntil,
tap
} from 'rxjs/operators';

import { PaginationResponse, TargetResponse, TranslationTargetUnitResponse } from '../../../models';
import { TranslationService } from '../../core/translation.service';
Expand Down Expand Up @@ -49,6 +61,44 @@ export class TranslationTargetService {
);
}

updateUnitOnChange(
unit: TranslationTargetUnitResponse,
controls: { target: AbstractControl; state: AbstractControl },
until: Observable<void>
) {
// The startWith, skip combination is necessary to deal with an IE11 bug
controls.target.valueChanges
.pipe(
takeUntil(until),
startWith(controls.target.value),
debounceTime(500),
distinctUntilChanged(),
skip(1),
tap(target =>
target
? controls.state.enable({ emitEvent: false })
: controls.state.disable({ emitEvent: false })
),
switchMap(target => this.updateUnit({ ...unit, target, state: controls.state.value }))
)
.subscribe(r => controls.state.setValue(r.state, { emitEvent: false }));
controls.state.valueChanges
.pipe(
takeUntil(until),
startWith(controls.state.value),
distinctUntilChanged(),
skip(1),
switchMap(state =>
this.updateUnit({
...unit,
target: controls.target.value,
state
})
)
)
.subscribe();
}

updateUnit(unit: Partial<TranslationTargetUnitResponse>) {
if (unit.target === '' && unit.state !== 'initial') {
unit.state = 'initial';
Expand Down
20 changes: 20 additions & 0 deletions src/app/target/core/xlf-element-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';

const parser = new DOMParser();

export function xlfElementValidator(source: string): ValidatorFn {
const doc = parser.parseFromString(`<root>${source}</root>`, 'text/xml');
const placeholders = Array.from<Element>(doc.documentElement.childNodes as any)
.filter(n => n.nodeType !== doc.TEXT_NODE)
.map(p => p.outerHTML);
return !placeholders.length ||
source.startsWith('{VAR_PLURAL') ||
source.startsWith('{VAR_SELECT')
? () => null
: (control: AbstractControl) => {
const value: string = control.value;
return typeof value !== 'string' || !value || placeholders.every(p => value.indexOf(p) >= 0)
? null
: { placeholders };
};
}
2 changes: 1 addition & 1 deletion src/app/target/orphans/orphans.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
mat-cell
[matTooltip]="row.locations?.join('\n')"
[matTooltipDisabled]="!row.locations?.length"
matTooltipClass="tooltip-locations"
matTooltipClass="tooltip-linebreak"
*matCellDef="let row"
>
{{ row.id }}
Expand Down
5 changes: 5 additions & 0 deletions src/app/target/target-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ImportComponent } from './import/import.component';
import { OrphansComponent } from './orphans/orphans.component';
import { TargetComponent } from './target/target.component';
import { TranslateComponent } from './translate/translate.component';
import { UnitComponent } from './unit/unit.component';

const routes: Routes = [
{
Expand All @@ -16,6 +17,10 @@ const routes: Routes = [
path: '',
component: TranslateComponent
},
{
path: 'unit/:unitId',
component: UnitComponent
},
{
path: 'import',
component: ImportComponent
Expand Down
4 changes: 3 additions & 1 deletion src/app/target/target.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ import { OrphansComponent } from './orphans/orphans.component';
import { TargetRoutingModule } from './target-routing.module';
import { TargetComponent } from './target/target.component';
import { TranslateComponent } from './translate/translate.component';
import { UnitComponent } from './unit/unit.component';

@NgModule({
declarations: [
TranslateComponent,
ExportComponent,
ImportComponent,
TargetComponent,
OrphansComponent
OrphansComponent,
UnitComponent
],
imports: [
CommonModule,
Expand Down
55 changes: 6 additions & 49 deletions src/app/target/translate/translate-datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@ import { FormControl, FormGroup } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { BehaviorSubject, merge, Observable, Subject } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
map,
skip,
startWith,
switchMap,
takeUntil,
tap
} from 'rxjs/operators';
import { debounceTime, map, startWith, switchMap, tap } from 'rxjs/operators';

import { FormTargetUnit } from '../../../models';
import { TranslationTargetService } from '../core/translation-target.service';
import { xlfElementValidator } from '../core/xlf-element-validator';

export class TranslateDataSource extends DataSource<FormTargetUnit> {
totalEntries: Observable<number>;
Expand Down Expand Up @@ -56,46 +48,11 @@ export class TranslateDataSource extends DataSource<FormTargetUnit> {
unitPage._embedded!.entries.map(u => {
const unit: FormTargetUnit = {
...u,
target: new FormControl(u.target),
state: new FormControl(u.state)
target: new FormControl(u.target, xlfElementValidator(u.source)),
state: new FormControl({ value: u.state, disabled: !u.target })
};
if (!u.target) {
unit.state.disable();
}

// The startWith, skip combination is necessary to deal with an IE11 bug
unit.target.valueChanges
.pipe(
takeUntil(this._destroy),
startWith(unit.target.value),
debounceTime(500),
distinctUntilChanged(),
skip(1),
tap(target =>
target
? unit.state.enable({ emitEvent: false })
: unit.state.disable({ emitEvent: false })
),
switchMap(target =>
this._translationTargetService.updateUnit({ ...u, target, state: unit.state.value })
)
)
.subscribe(r => unit.state.setValue(r.state, { emitEvent: false }));
unit.state.valueChanges
.pipe(
takeUntil(this._destroy),
startWith(unit.state.value),
distinctUntilChanged(),
skip(1),
switchMap(state =>
this._translationTargetService.updateUnit({
...u,
target: unit.target.value,
state
})
)
)
.subscribe();
this._translationTargetService.updateUnitOnChange(u, unit, this._destroy);
unit.target.markAsTouched();
return unit;
})
)
Expand Down
30 changes: 27 additions & 3 deletions src/app/target/translate/translate.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
mat-cell
[matTooltip]="row.locations?.join('\n')"
[matTooltipDisabled]="!row.locations?.length"
matTooltipClass="tooltip-locations"
matTooltipClass="tooltip-linebreak"
*matCellDef="let row"
>
{{ row.id }}
Expand Down Expand Up @@ -34,6 +34,12 @@
placeholder="Translation"
[formControl]="row.target"
></textarea>
<mat-hint *ngIf="row.target.errors?.placeholders as placeholders"
>Expected {{ placeholders.length }} placeholders
<mat-icon [matTooltip]="placeholders.join('\n')" matTooltipClass="tooltip-linebreak"
>info</mat-icon
>
</mat-hint>
</mat-form-field>
</td>
</ng-container>
Expand All @@ -50,6 +56,18 @@
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="unit-link">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let row">
<a
[routerLink]="['./unit', row.id, queryParams | async]"
mat-icon-button
matTooltip="Edit unit"
>
<mat-icon>edit</mat-icon>
</a>
</td>
</ng-container>

<ng-container matColumnDef="id-filter">
<th mat-header-cell *matHeaderCellDef>
Expand Down Expand Up @@ -102,7 +120,10 @@

<tr
mat-header-row
*matHeaderRowDef="['id', 'description', 'meaning', 'source', 'target', 'state']; sticky: true"
*matHeaderRowDef="
['id', 'description', 'meaning', 'source', 'target', 'state', 'unit-link'];
sticky: true
"
></tr>
<tr
mat-header-row
Expand All @@ -120,7 +141,10 @@
></tr>
<tr
mat-row
*matRowDef="let row; columns: ['id', 'description', 'meaning', 'source', 'target', 'state']"
*matRowDef="
let row;
columns: ['id', 'description', 'meaning', 'source', 'target', 'state', 'unit-link']
"
></tr>
</table>

Expand Down
4 changes: 4 additions & 0 deletions src/app/target/translate/translate.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ table {
&.mat-column-state {
width: 10rem;
}

&.mat-column-unit-link {
width: 2rem;
}
}

mat-form-field {
Expand Down
2 changes: 2 additions & 0 deletions src/app/target/translate/translate.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class TranslateComponent implements AfterViewInit, OnDestroy {
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatTable) table!: MatTable<FormTargetUnit>;
dataSource!: TranslateDataSource;
queryParams: Observable<Params>;
filter: FormGroup;
private _paginationHelper: PaginationHelper;
private _destroy = new Subject<void>();
Expand All @@ -40,6 +41,7 @@ export class TranslateComponent implements AfterViewInit, OnDestroy {
formBuilder: FormBuilder
) {
this._paginationHelper = new PaginationHelper(this, this._route, this._router);
this.queryParams = this._route.queryParams;
this.filter = formBuilder.group({
id: '',
description: '',
Expand Down
56 changes: 56 additions & 0 deletions src/app/target/unit/unit.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<h2>{{ (unit | async)?.id }}</h2>

<ng-container *ngIf="form | async as formGroup">
<form [formGroup]="formGroup">
<mat-form-field appearance="standard">
<mat-label>Description</mat-label>
<input matInput formControlName="description" />
</mat-form-field>

<mat-form-field appearance="standard">
<mat-label>Meaning</mat-label>
<input matInput formControlName="meaning" />
</mat-form-field>

<mat-form-field appearance="standard">
<mat-label>Source</mat-label>
<textarea matInput formControlName="source" cdkTextareaAutosize></textarea>
<button
type="button"
mat-icon-button
matSuffix
matTooltip="Copy source to clipboard"
(click)="copySourceToClipboard(formGroup.get('source').value)"
>
<mat-icon>file_copy</mat-icon>
</button>
</mat-form-field>

<mat-form-field appearance="standard">
<mat-label>Target</mat-label>
<textarea matInput formControlName="target" cdkTextareaAutosize></textarea>
<mat-hint *ngIf="formGroup.get('target').errors?.placeholders as placeholders"
>Expected {{ placeholders.length }} placeholders
<mat-icon [matTooltip]="placeholders.join('\n')" matTooltipClass="tooltip-linebreak"
>info</mat-icon
>
</mat-hint>
</mat-form-field>

<mat-form-field appearance="standard">
<mat-label>State</mat-label>
<mat-select formControlName="state">
<mat-option *ngIf="formGroup.get('state').value === 'initial'" value="initial"
>Initial</mat-option
>
<mat-option value="translated">Translated</mat-option>
<mat-option value="reviewed">Reviewed</mat-option>
<mat-option value="final">Final</mat-option>
</mat-select>
</mat-form-field>
</form>
</ng-container>

<a mat-raised-button color="primary" routerLink="../.." [queryParams]="params | async">
<mat-icon>keyboard_backspace</mat-icon> Back
</a>
Loading

0 comments on commit 9d1b8a5

Please sign in to comment.