From cbb84649c79cb64180d038e7202279e562d8361e Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 27 Jun 2026 22:45:13 -0700 Subject: [PATCH] Pull out the zone list into a sub-component --- ...r-template-editor-zone-list.component.html | 34 +++++++++ ...emplate-editor-zone-list.component.spec.ts | 72 +++++++++++++++++++ ...ocr-template-editor-zone-list.component.ts | 41 +++++++++++ .../ocr-template-editor.component.html | 37 +++------- .../ocr-template-editor.component.ts | 2 + 5 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.html create mode 100644 src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.spec.ts create mode 100644 src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.ts diff --git a/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.html b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.html new file mode 100644 index 000000000..67f2f6eb8 --- /dev/null +++ b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.html @@ -0,0 +1,34 @@ +@if (zones.length === 0) { +

+ No zones defined. Load a document preview and draw rectangles to add zones. +

+} + +
+ @for (zone of zones; track $index; let i = $index) { +
+
+
+ + {{ zone.name }} + +
+
+ {{ getZoneTargetName(zone) }} - {{ zone.width }}x{{ zone.height }}px + p.{{ zonePage(zone) }} +
+
+
+ + +
+
+ } +
diff --git a/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.spec.ts b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.spec.ts new file mode 100644 index 000000000..a7895fcee --- /dev/null +++ b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.spec.ts @@ -0,0 +1,72 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' +import { CustomField } from 'src/app/data/custom-field' +import { OcrTemplateZone } from 'src/app/data/ocr-template' +import { OcrTemplateEditorZoneListComponent } from './ocr-template-editor-zone-list.component' + +function zone(overrides: Partial = {}): OcrTemplateZone { + return { + name: 'Zone 1', + target: 'custom_field', + custom_field: 7, + x: 10, + y: 20, + width: 30, + height: 40, + page: 1, + ocr_language: 'eng', + transform: 'strip', + validation_regex: '', + order: 0, + ...overrides, + } +} + +describe('OcrTemplateEditorZoneListComponent', () => { + let fixture: ComponentFixture + let component: OcrTemplateEditorZoneListComponent + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + OcrTemplateEditorZoneListComponent, + NgxBootstrapIconsModule.pick(allIcons), + ], + }).compileComponents() + + fixture = TestBed.createComponent(OcrTemplateEditorZoneListComponent) + component = fixture.componentInstance + }) + + it('shows empty state when no zones are defined', () => { + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('No zones defined') + }) + + it('renders zone target, size, and page', () => { + component.zones = [zone()] + component.customFields = [{ id: 7, name: 'Invoice Number' } as CustomField] + fixture.detectChanges() + + const text = fixture.nativeElement.textContent + expect(text).toContain('Zone 1') + expect(text).toContain('Invoice Number') + expect(text).toContain('30x40px') + expect(text).toContain('p.1') + }) + + it('emits select and remove events', () => { + component.zones = [zone()] + const selectSpy = jest.spyOn(component.zoneSelected, 'emit') + const removeSpy = jest.spyOn(component.zoneRemoved, 'emit') + fixture.detectChanges() + + const buttons = fixture.nativeElement.querySelectorAll('button') + buttons[0].click() + buttons[1].click() + + expect(selectSpy).toHaveBeenCalledWith(0) + expect(removeSpy).toHaveBeenCalledWith(0) + }) +}) diff --git a/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.ts b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.ts new file mode 100644 index 000000000..0a633842c --- /dev/null +++ b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor-zone-list/ocr-template-editor-zone-list.component.ts @@ -0,0 +1,41 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core' +import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' +import { CustomField } from 'src/app/data/custom-field' +import { OCR_BUILTIN_TARGETS, OcrTemplateZone } from 'src/app/data/ocr-template' +import { getZonePage } from '../zone-geometry' + +@Component({ + selector: 'pngx-ocr-template-zone-list', + imports: [NgxBootstrapIconsModule], + templateUrl: './ocr-template-editor-zone-list.component.html', +}) +export class OcrTemplateEditorZoneListComponent { + @Input() zones: OcrTemplateZone[] = [] + @Input() selectedZoneIndex: number | null = null + @Input() previewPage = 0 + @Input() previewPageCount: number | null = null + @Input() customFields: CustomField[] = [] + + @Output() zoneSelected = new EventEmitter() + @Output() zoneRemoved = new EventEmitter() + + zonePage(zone: OcrTemplateZone): number { + return getZonePage(zone, this.previewPage, this.previewPageCount) + } + + getZoneTargetName(zone: OcrTemplateZone): string { + const target = zone.target || 'custom_field' + if (target === 'custom_field') { + return zone.custom_field + ? this.getCustomFieldName(zone.custom_field) + : $localize`(no field)` + } + return OCR_BUILTIN_TARGETS.find((t) => t.id === target)?.name ?? target + } + + private getCustomFieldName(id: number): string { + return ( + this.customFields.find((field) => field.id === id)?.name ?? `Field #${id}` + ) + } +} 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 dd17eda61..b7792c073 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 @@ -82,34 +82,15 @@
  • Zones {{ template.zones.length }} - @if (template.zones.length === 0) { -

    - No zones defined. Load a document preview and draw rectangles to add zones. -

    - } -
    - @for (zone of template.zones; track $index; let i = $index) { -
    -
    -
    {{ zone.name }}
    -
    - {{ getZoneTargetName(zone) }} - {{ zone.width }}x{{ zone.height }}px p.{{ zonePage(zone) }} -
    -
    -
    - - -
    -
    - } -
    +
  • diff --git a/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.ts b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.ts index 97c094a0a..f862c0186 100644 --- a/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.ts +++ b/src-ui/src/app/components/manage/ocr-templates/ocr-template-editor/ocr-template-editor.component.ts @@ -52,6 +52,7 @@ import { DocumentTypeService } from 'src/app/services/rest/document-type.service import { DocumentService } from 'src/app/services/rest/document.service' import { OcrTemplateService } from 'src/app/services/rest/ocr-template.service' import { ToastService } from 'src/app/services/toast.service' +import { OcrTemplateEditorZoneListComponent } from './ocr-template-editor-zone-list/ocr-template-editor-zone-list.component' import { DisplayRect, DrawingRect, @@ -86,6 +87,7 @@ type ActiveTab = 'settings' | 'zones' | 'zone' NgbTypeaheadModule, NgSelectModule, NgxBootstrapIconsModule, + OcrTemplateEditorZoneListComponent, ], templateUrl: './ocr-template-editor.component.html', styleUrls: ['./ocr-template-editor.component.scss'],