Drop the silly mode toggle

This commit is contained in:
shamoon committed 2026-10-19 23:04:35 -07:00
1 parent e028257da5
commit a4fb9bf2b0
4 files changed
+72 -97

No files matched your search

@@ -176,53 +176,43 @@
</div>
<div class="col-lg-8">
<div class="d-flex flex-wrap align-items-center gap-2 mb-2" role="toolbar" aria-label="Document preview" i18n-aria-label>
<div class="flex-grow-1">
<label class="visually-hidden" for="preview-document" i18n>Sample document</label>
<div class="input-group input-group-sm">
<span class="input-group-text">
@if (searchStatus() === 'loading') {
<span class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden" i18n>Searching...</span></span>
} @else {
<i-bs name="file-text"></i-bs>
}
</span>
<input
id="preview-document"
type="text"
class="form-control"
[(ngModel)]="previewDocModel"
[ngbTypeahead]="searchDocuments"
#documentSearch="ngbTypeahead"
[inputFormatter]="documentFormatter"
[resultFormatter]="documentFormatter"
[resultTemplate]="documentResult"
(input)="searchStatus.set('idle')"
(selectItem)="onPreviewDocSelected($event)"
[editable]="false"
container="body"
popupClass="ocr-template-document-results col-11 col-md-6 overflow-y-auto"
placeholder="Search for a sample document..."
i18n-placeholder
/>
@if (previewDocId) {
<button type="button" class="btn btn-outline-secondary" (click)="clearPreviewDoc(); documentSearch.dismissPopup()" title="Clear sample document" i18n-title aria-label="Clear sample document" i18n-aria-label>
<i-bs name="x"></i-bs>
</button>
<div class="mb-2">
<label class="visually-hidden" for="preview-document" i18n>Sample document</label>
<div class="input-group input-group-sm">
<span class="input-group-text">
@if (searchStatus() === 'loading') {
<span class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden" i18n>Searching...</span></span>
} @else {
<i-bs name="file-text"></i-bs>
}
</div>
<ng-template #documentResult let-document="result">
<span class="d-block text-wrap text-break">{{ documentFormatter(document) }}</span>
</ng-template>
</div>
<div class="btn-group btn-group-sm text-nowrap" role="group" aria-label="Editing mode" i18n-aria-label>
<button type="button" class="btn btn-outline-primary" [class.active]="overlayMode === 'select'" [attr.aria-pressed]="overlayMode === 'select'" [disabled]="!imageLoaded" (click)="setOverlayMode('select')" title="Select and move zones" i18n-title>
<i-bs name="cursor" class="me-1"></i-bs><span i18n>Select</span>
</button>
<button type="button" class="btn btn-outline-primary" [class.active]="overlayMode === 'draw'" [attr.aria-pressed]="overlayMode === 'draw'" [disabled]="!imageLoaded" (click)="setOverlayMode('draw')" title="Draw a zone" i18n-title>
<i-bs name="pencil" class="me-1"></i-bs><span i18n>Draw</span>
</button>
</span>
<input
id="preview-document"
type="text"
class="form-control"
[(ngModel)]="previewDocModel"
[ngbTypeahead]="searchDocuments"
#documentSearch="ngbTypeahead"
[inputFormatter]="documentFormatter"
[resultFormatter]="documentFormatter"
[resultTemplate]="documentResult"
(input)="searchStatus.set('idle')"
(selectItem)="onPreviewDocSelected($event)"
[editable]="false"
container="body"
popupClass="ocr-template-document-results col-11 col-md-6 overflow-y-auto"
placeholder="Search for a sample document..."
i18n-placeholder
/>
@if (previewDocId) {
<button type="button" class="btn btn-outline-secondary" (click)="clearPreviewDoc(); documentSearch.dismissPopup()" title="Clear sample document" i18n-title aria-label="Clear sample document" i18n-aria-label>
<i-bs name="x"></i-bs>
</button>
}
</div>
<ng-template #documentResult let-document="result">
<span class="d-block text-wrap text-break">{{ documentFormatter(document) }}</span>
</ng-template>
</div>
@if (searchStatus() === 'empty' || searchStatus() === 'error') {
<div class="small mb-2" role="status" [class.text-danger]="searchStatus() === 'error'" [class.text-muted]="searchStatus() === 'empty'">
@@ -258,7 +248,6 @@
<svg
#zoneOverlay
class="zone-overlay position-absolute top-0 start-0 w-100 h-100"
[class.zone-overlay-drawing]="overlayMode === 'draw'"
[attr.viewBox]="overlayViewBox()"
preserveAspectRatio="none"
[style.cursor]="overlayCursor"
@@ -289,7 +278,7 @@
[attr.fill]="zoneColor(i)"
>{{ zoneLabel(zone, i) }}</text>
@if (selectedZoneIndex === i && overlayMode === 'select') {
@if (selectedZoneIndex === i) {
@for (handle of resizeHandles(rect); track handle.handle) {
<rect
class="zone-resize-handle"
@@ -20,12 +20,6 @@
}
.zone-overlay {
touch-action: pan-x pan-y;
}
.zone-overlay-drawing,
.zone-rect,
.zone-resize-handle {
touch-action: none;
}
@@ -171,18 +171,30 @@ describe('OcrTemplateEditorComponent', () => {
expect(fixture.nativeElement.textContent).toBe('empty')
})
it('does not create zones by dragging empty space in Select mode', () => {
it('clears the selection on a click in empty space without adding a zone', () => {
prepareOverlay()
component.onOverlayPointerDown(pointer(100, 100))
component.onOverlayPointerMove(pointer(300, 200))
component.onOverlayPointerUp(pointer(300, 200))
component.template.zones = [zone()]
component.selectZone(0)
component.onOverlayPointerDown(pointer(600, 600))
component.onOverlayPointerUp(pointer(602, 601))
expect(component.template.zones).toEqual([])
expect(component.template.zones.length).toBe(1)
expect(component.selectedZoneIndex).toBeNull()
})
it('draws within the page bounds, uses the release position and returns to Select', () => {
it('moves a zone when dragging on it instead of drawing', () => {
prepareOverlay()
component.template.zones = [zone()]
component.onOverlayPointerDown(pointer(150, 150))
component.onOverlayPointerUp(pointer(250, 200))
expect(component.template.zones.length).toBe(1)
expect(component.template.zones[0]).toMatchObject({ x: 200, y: 150 })
expect(component.selectedZoneIndex).toBe(0)
})
it('draws in empty space within the page bounds, using the release position', () => {
prepareOverlay()
component.setOverlayMode('draw')
component.onOverlayPointerDown(pointer(100, 100))
component.onOverlayPointerUp(pointer(1200, 250))
@@ -193,12 +205,10 @@ describe('OcrTemplateEditorComponent', () => {
height: 150,
})
expect(component.selectedZoneIndex).toBe(0)
expect(component.overlayMode).toBe('select')
})
it('discards a drawing on Escape and ignores other pointers', () => {
prepareOverlay()
component.setOverlayMode('draw')
component.onOverlayPointerDown(pointer(100, 100))
component.onOverlayPointerUp(pointer(300, 200, 2))
expect(component.drawingRect()).not.toBeNull()
@@ -206,7 +216,6 @@ describe('OcrTemplateEditorComponent', () => {
component.cancelDrawing()
component.onOverlayPointerUp(pointer(300, 200))
expect(component.template.zones).toEqual([])
expect(component.overlayMode).toBe('select')
})
it('opens the zone in the Zones tab and returns to the list', () => {
@@ -88,7 +88,6 @@ import {
type ActiveTab = 'settings' | 'zones'
type ZoneFieldSelection = OcrBuiltinTarget | number | null
type OverlayMode = 'select' | 'draw'
type OverlayInteraction =
| { kind: 'idle' }
| { kind: 'drawing'; pointerId: number; rect: DrawingRect }
@@ -248,8 +247,7 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
selectedZoneIndex: number | null = null
private overlayInteraction: OverlayInteraction = NO_OVERLAY_INTERACTION
overlayMode: OverlayMode = 'select'
overlayCursor = 'default'
overlayCursor = 'crosshair'
private readonly zoneTest = signal<ZoneTest | null>(null)
@@ -484,15 +482,9 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
this.previewError = $localize`Could not load this page.`
}
setOverlayMode(mode: OverlayMode) {
this.stopOverlayInteraction()
this.overlayMode = mode
this.overlayCursor = mode === 'draw' ? 'crosshair' : 'default'
}
@HostListener('document:keydown.escape')
cancelDrawing() {
this.setOverlayMode('select')
this.stopOverlayInteraction()
}
onOverlayPointerDown(event: PointerEvent) {
@@ -501,23 +493,6 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
if (!this.imageLoaded || !point) return
event.preventDefault()
if (this.overlayMode === 'draw') {
this.clearZoneTest()
this.overlayInteraction = {
kind: 'drawing',
pointerId: event.pointerId,
rect: {
startX: point.x,
startY: point.y,
endX: point.x,
endY: point.y,
},
}
this.selectedZoneIndex = null
this.overlayRef.nativeElement.setPointerCapture(event.pointerId)
return
}
if (this.selectedZoneIndex !== null) {
const handle = this.findHandleAt(point, this.selectedZoneIndex)
if (handle) {
@@ -551,8 +526,21 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
return
}
// Empty space: start drawing a new zone. A click without a drag just clears
// the selection (see onOverlayPointerUp).
this.clearZoneTest()
this.selectedZoneIndex = null
this.overlayInteraction = {
kind: 'drawing',
pointerId: event.pointerId,
rect: {
startX: point.x,
startY: point.y,
endX: point.x,
endY: point.y,
},
}
this.overlayRef.nativeElement.setPointerCapture(event.pointerId)
}
onOverlayPointerMove(event: PointerEvent) {
@@ -594,10 +582,6 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
}
private updateOverlayCursor(point: Point) {
if (this.overlayMode === 'draw') {
this.overlayCursor = 'crosshair'
return
}
if (this.selectedZoneIndex !== null) {
const handle = this.findHandleAt(point, this.selectedZoneIndex)
if (handle) {
@@ -605,7 +589,7 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
return
}
}
this.overlayCursor = this.findZoneAt(point) !== null ? 'move' : 'default'
this.overlayCursor = this.findZoneAt(point) !== null ? 'move' : 'crosshair'
}
onOverlayPointerUp(event: PointerEvent) {
@@ -624,7 +608,6 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
}
this.template.zones.push(this.createZoneFromRect(rect))
this.setOverlayMode('select')
this.selectZone(this.template.zones.length - 1)
}
@@ -662,7 +645,7 @@ export class OcrTemplateEditorComponent implements OnInit, OnDestroy {
private stopOverlayInteraction() {
const interaction = this.overlayInteraction
this.overlayInteraction = NO_OVERLAY_INTERACTION
this.overlayCursor = this.overlayMode === 'draw' ? 'crosshair' : 'default'
this.overlayCursor = 'crosshair'
const overlay = this.overlayRef?.nativeElement
if (
interaction.kind !== 'idle' &&