diff --git a/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.html b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.html index 38a02d65f..a460b7dcc 100644 --- a/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.html +++ b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.html @@ -34,62 +34,88 @@ Zones {{ template.zones.length }} @if (selectedZone; as zone) { - - + + All zones - - Delete zone + + @if (zoneTesting) { + + } @else { + + } + Test + + + - - Zone Name - - + @if (zoneTestResult; as result) { + @if (result.error) { + {{ result.error }} + } @else { + + OCR text + {{ result.raw_text || '(nothing detected)' }} + Value + {{ result.value || '(empty)' }} + @if (result.regex) { + Validation + + @if (result.regex_match) { + Regex matches + } @else { + Regex does not match + } + + } + + } + } - - Page - - Page this zone is on. Use -1 for the last page. Set automatically when you draw it. - + - - Field - - - - @for (t of builtinTargets; track t.id) { - {{ t.name }} - } - - - @for (cf of customFields; track cf.id) { - {{ cf.name }} ({{ cf.data_type }}) - } - - - - - + + + @if (showQuickCreate) { + + + Create Custom Field + + Field Name + + + + Field Type + + @for (t of quickCreateTypes; track t.id) { + {{ t.name }} + } + + + + + Create & Assign + + + Cancel + + + - Write the extracted value to a custom field, or to a built-in field (Title, ASN, Date created). - + } @if (isFieldShared(zone)) { @@ -132,141 +158,62 @@ } - @if (showQuickCreate) { - - - Create Custom Field - - Field Name - - - - Field Type - - @for (t of quickCreateTypes; track t.id) { - {{ t.name }} - } - - - - - Create & Assign - - - Cancel - - - - - } - - - OCR Language - - - - - Transform - - @for (opt of transformOptions; track opt.id) { - {{ opt.name }} + + + @if (!isLastPageZone(zone)) { + } - + + + + + Processing + + + + + @if (zone.transform === dateTransform) { - - Date format - - @for (opt of dateFormatOptions; track opt.id) { - {{ opt.name }} - } - Custom... - - @if (usesCustomDateFormat(zone)) { - - - - - - - - Python date codes: - - %d day (01-31) - %m month (01-12) - %Y year, 4-digit - %y year, 2-digit - %b month name (Jan) - - Example: %d.%m.%Y -> 03.03.2026 - - } - - } - - - Validation Regex - - - - - {{ zone.x }}, {{ zone.y }} - {{ zone.width }}x{{ zone.height }}px - - - - Test - @if (!previewDocId) { - - No sample document selected. - - } @else { - - @if (zoneTesting) { - - } - Test this zone - - @if (zoneTestResult; as result) { - @if (result.error) { - {{ result.error }} - } @else { - - OCR text - {{ result.raw_text || '(nothing detected)' }} - Value - {{ result.value || '(empty)' }} - @if (result.regex) { - Validation - - @if (result.regex_match) { - Regex matches - } @else { - Regex does not match - } - - } - - } + + @if (usesCustomDateFormat(zone)) { + + + + + + + + Python date codes: + + %d day (01-31) + %m month (01-12) + %Y year, 4-digit + %y year, 2-digit + %b month name (Jan) + + Example: %d.%m.%Y -> 03.03.2026 + } } + + } @else { = { standalone: true, imports: [ PageHeaderComponent, + NumberComponent, TextComponent, SelectComponent, SwitchComponent, @@ -140,7 +141,6 @@ const RESIZE_CURSOR: Record = { NgbNavModule, NgbPopoverModule, NgbTypeaheadModule, - NgSelectModule, NgxBootstrapIconsModule, OcrTemplateEditorZoneListComponent, ], @@ -191,7 +191,23 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy { zones: [], } - customFields: CustomField[] = [] + private _customFields: CustomField[] = [] + /** Items for the zone field select: built-in targets, then custom fields. */ + fieldItems: { id: ZoneFieldSelection; name: string }[] = [ + ...OCR_BUILTIN_TARGETS, + ] + + get customFields(): CustomField[] { + return this._customFields + } + + set customFields(fields: CustomField[]) { + this._customFields = fields + this.fieldItems = [ + ...OCR_BUILTIN_TARGETS, + ...fields.map((cf) => ({ id: cf.id, name: cf.name })), + ] + } documentTypes: DocumentType[] = [] transformOptions = TRANSFORM_OPTIONS builtinTargets = OCR_BUILTIN_TARGETS @@ -199,6 +215,11 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy { ocrLanguageOptions = OCR_LANGUAGE_OPTIONS dateTransform = OCR_ZONE_TRANSFORM.Date customDateFormatChoice = CUSTOM_DATE_FORMAT_CHOICE + noSampleDocumentTitle = $localize`Select a sample document to test this zone` + dateFormatItems = [ + ...DATE_FORMAT_OPTIONS, + { id: CUSTOM_DATE_FORMAT_CHOICE, name: $localize`Custom...` }, + ] isNew = true saving = false @@ -231,18 +252,18 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy { overlayMode: OverlayMode = 'select' overlayCursor = 'default' - private zoneTest: ZoneTest | null = null + private readonly zoneTest = signal(null) get imageLoaded(): boolean { return this.previewStatus === 'ready' } get zoneTestResult(): OcrZoneTestResult | null { - return this.isCurrentZoneTest() ? this.zoneTest.result : null + return this.isCurrentZoneTest() ? this.zoneTest().result : null } get zoneTesting(): boolean { - return this.isCurrentZoneTest() && this.zoneTest.result === null + return this.isCurrentZoneTest() && this.zoneTest().result === null } showQuickCreate = false @@ -454,6 +475,14 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy { return getZonePage(zone, this.previewPage, this.previewPageCount) } + isLastPageZone(zone: OcrTemplateZone): boolean { + return zone.page === -1 + } + + setLastPageZone(zone: OcrTemplateZone, lastPage: boolean) { + zone.page = lastPage ? -1 : this.previewPage + 1 + } + private isOnCurrentPage(zone: OcrTemplateZone): boolean { return isZoneOnPage(zone, this.previewPage, this.previewPageCount) } @@ -835,31 +864,27 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy { request: this.zoneTestRequest(zone), result: null, } - this.zoneTest = test + this.zoneTest.set(test) this.templateService .testZone(test.documentId, test.request) .pipe(takeUntil(this.destroy$), takeUntil(this.cancelZoneTest$)) .subscribe({ - next: (res) => { - test.result = res - this.changeDetector.markForCheck() - }, - error: (err) => { - test.result = { - error: err.error?.error || $localize`Test failed`, - } - this.changeDetector.markForCheck() - }, + next: (res) => this.zoneTest.set({ ...test, result: res }), + error: (err) => + this.zoneTest.set({ + ...test, + result: { error: err.error?.error || $localize`Test failed` }, + }), }) } private clearZoneTest() { this.cancelZoneTest$.next() - this.zoneTest = null + this.zoneTest.set(null) } private isCurrentZoneTest(): boolean { - const test = this.zoneTest + const test = this.zoneTest() if ( !test || test.zone !== this.selectedZone || @@ -1067,10 +1092,10 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy { return dt ? dt.name : `Type #${id}` } - openQuickCreate(zoneIndex: number | null) { + openQuickCreate(zoneIndex: number | null, name?: string) { if (zoneIndex === null) return this.quickCreateForZoneIndex = zoneIndex - this.quickCreateName = this.template.zones[zoneIndex]?.name || '' + this.quickCreateName = name || this.template.zones[zoneIndex]?.name || '' this.quickCreateType = CustomFieldDataType.String this.showQuickCreate = true }
{{ result.raw_text || '(nothing detected)' }}
{{ result.value || '(empty)' }}
Python date codes:
%d
%m
%Y
%y
%b
%d.%m.%Y
- No sample document selected. -