LPXXX editor continued
authorBill Erickson <berickxx@gmail.com>
Mon, 25 Nov 2019 23:18:30 +0000 (18:18 -0500)
committerBill Erickson <berickxx@gmail.com>
Fri, 6 Dec 2019 15:37:03 +0000 (10:37 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.html
Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts
Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts

index e5da09b..25289e1 100644 (file)
@@ -8,7 +8,7 @@
     [egContextMenu]="contextMenuEntries()"
     (menuItemSelected)="contextMenuChange($event.value)"
     (keydown)="inputKeyDown($event)"
-    (focus)="focusBigText()"
+    (focus)="selectText()"
     (input)="bigTextValueChange()">
   </div>
 </ng-container>
@@ -25,7 +25,7 @@
     [egContextMenu]="contextMenuEntries()"
     (menuItemSelected)="contextMenuChange($event.value)"
     (keydown)="inputKeyDown($event)"
-    (focus)="$event.target.select()"
+    (focus)="selectText()"
     [ngModel]="getContent()"
     (ngModelChange)="setContent($event)"
   />
index 62ed01e..ac7f5eb 100644 (file)
@@ -2,7 +2,7 @@ import {ElementRef, Component, Input, Output, OnInit, EventEmitter,
     AfterViewInit, Renderer2} from '@angular/core';
 import {filter} from 'rxjs/operators';
 import {MarcRecord, MarcField, MarcSubfield} from './marcrecord';
-import {MarcEditContext, FieldFocusRequest} from './editor-context';
+import {MarcEditContext, FieldFocusRequest, MARC_EDITABLE_FIELD_TYPE} from './editor-context';
 import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
 import {TagTableService} from './tagtable.service';
 
@@ -20,7 +20,7 @@ export class EditableContentComponent implements OnInit, AfterViewInit {
 
     @Input() context: MarcEditContext;
     @Input() field: MarcField;
-    @Input() fieldType: 'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv' = null;
+    @Input() fieldType: MARC_EDITABLE_FIELD_TYPE = null;
 
     // read-only field text.  E.g. 'LDR'
     @Input() fieldText: string = null;
@@ -64,14 +64,31 @@ export class EditableContentComponent implements OnInit, AfterViewInit {
                 return;
             }
 
-            if (this.bigText) {
-                this.focusBigText();
-            } else {
-                this.editInput.select();
-            }
+            this.selectText(req);
         });
     }
 
+    selectText(req?: FieldFocusRequest) {
+        if (this.bigText) {
+            this.focusBigText();
+        } else {
+            this.editInput.select();
+        }
+
+        if (!req) {
+            // Focus request may have come from keyboard navigation, 
+            // clicking, etc.  Model the event as a focus request
+            // so our context can track it just like an auto-focus.
+            req = {
+                fieldId: this.field.fieldId, 
+                target: this.fieldType,
+                sfOffset: this.subfield ? this.subfield[2] : null
+            };
+        }
+
+        this.context.lastFocused = req;
+    }
+
     setupFieldType() {
         const content = this.getContent();
 
@@ -205,7 +222,7 @@ export class EditableContentComponent implements OnInit, AfterViewInit {
                 if (evt.ctrlKey) {
                     // ctrl+enter == insert stub field after focused field
                     // ctrl+shift+enter == insert stub field before focused field
-                    this.insertField(evt.shiftKey);
+                    this.context.insertStubField(this.field, evt.shiftKey);
                 }
 
                 evt.preventDefault(); // Bare newlines not allowed.
@@ -215,39 +232,44 @@ export class EditableContentComponent implements OnInit, AfterViewInit {
 
                 if (evt.ctrlKey) { 
                     // ctrl+delete == delete whole field
-                    this.deleteField();
+                    this.context.deleteField(this.field);
                     evt.preventDefault();
 
                 } else if (evt.shiftKey && this.subfield) {
                     // shift+delete == delete subfield
 
-                    this.deleteSubfield();
+                    this.context.deleteSubfield(this.field, this.subfield);
                     evt.preventDefault();
                 }
 
                 break;
 
             case 'ArrowDown': 
+
                 if (evt.ctrlKey) {
                     // ctrl+down == copy current field down one
-                    this.record.insertFieldsAfter(
+                    this.context.insertField(
                         this.field, this.record.cloneField(this.field));
+                } else {
+                    // avoid dupe focus requests
+                    this.context.focusNextTag(this.field);
                 }
 
-                // down == move focus to tag of previous field
-                this.context.focusNextTag(this.field);
                 evt.preventDefault();
                 break;
 
             case 'ArrowUp':
+
                 if (evt.ctrlKey) {
                     // ctrl+up == copy current field up one
-                    this.record.insertFieldsBefore(
-                        this.field, this.record.cloneField(this.field));
+                    this.context.insertField(
+                        this.field, this.record.cloneField(this.field), true);
+                } else {
+                    // avoid dupe focus requests
+                    this.context.focusPreviousTag(this.field);
                 }
 
                 // up == move focus to tag of previous field
-                this.context.focusPreviousTag(this.field);
                 evt.preventDefault();
                 break;
 
index 56655f0..11ce5fb 100644 (file)
@@ -4,20 +4,38 @@ import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
 
 /* Per-instance MARC editor context. */
 
+export type MARC_EDITABLE_FIELD_TYPE = 
+    'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv';
+
 export interface FieldFocusRequest {
     fieldId: number;
-    target: 'tag' | 'sfc' | 'sfv';
+    target: MARC_EDITABLE_FIELD_TYPE;
+
+    // in some cases, field IDs change out from under our feet (e.g.
+    // undo / redo applications) so track the tag offset as well
+    // so we can find our way back if needed.
+    tagOffset?: number;
 
     // focus a specific subfield by its offset
     sfOffset?: number; 
 }
 
+export interface UndoRedoAction {
+    breakerText: string;
+    position: FieldFocusRequest;
+}
+
 export class MarcEditContext {
 
     recordChange: EventEmitter<MarcRecord>;
     fieldFocusRequest: EventEmitter<FieldFocusRequest>;
     recordType: 'biblio' | 'authority' = 'biblio';
 
+    lastFocused: FieldFocusRequest = null;
+
+    undoStack: UndoRedoAction[] = [];
+    redoStack: UndoRedoAction[] = [];
+
     private _record: MarcRecord;
     set record(r: MarcRecord) {
         if (r !== this._record) {
@@ -39,10 +57,31 @@ export class MarcEditContext {
     requestFieldFocus(req: FieldFocusRequest) {
         // timeout allows for new components to be built before the
         // focus request is emitted.
-        setTimeout(() => this.fieldFocusRequest.emit(req));
+        setTimeout(() => {
+            this.fieldFocusRequest.emit(req);
+        });
+    }
+
+    getFieldOffset(fieldId: number): number {
+        for (let idx = 0; idx < this.record.fields.length; idx++) {
+            if (this.record.fields[idx].fieldId === fieldId) {
+                return idx;
+            }
+        }
+    }
+
+    recordChanging() {
+        this.lastFocused.tagOffset = 
+            this.getFieldOffset(this.lastFocused.fieldId);
+
+        this.undoStack.unshift({
+            breakerText: this.record.toBreaker(), 
+            position: this.lastFocused
+        });
     }
 
     add006() {
+        this.recordChanging();
         this.record.insertOrderedFields(
             this.record.newField({
                 tag : '006',
@@ -53,6 +92,7 @@ export class MarcEditContext {
 
 
     add007() {
+        this.recordChanging();
         this.record.insertOrderedFields(
             this.record.newField({
                 tag : '007',
@@ -62,6 +102,7 @@ export class MarcEditContext {
     }
 
     insertReplace008() {
+        this.recordChanging();
 
         // delete all of the 008s
         [].concat(this.record.field('008', true)).forEach(
@@ -79,6 +120,7 @@ export class MarcEditContext {
     // Adds a new empty subfield to the provided field at the
     // requested subfield position
     insertSubfield(field: MarcField, position: number) {
+        this.recordChanging();
 
         // array index 3 contains that position of the subfield
         // in the MARC field.  When splicing a new subfield into
@@ -97,6 +139,53 @@ export class MarcEditContext {
         });
     }
 
+    // Add stub field before or after the context field
+    insertStubField(field: MarcField, before?: boolean) {
+
+        const newField = this.record.newField(
+            {tag: '999', subfields: [[' ', '', 0]]});
+
+        this.insertField(field, newField, before);
+    }
+
+    insertField(contextField: MarcField, newField: MarcField, before?: boolean) {
+        this.recordChanging();
+
+        if (before) {
+            this.record.insertFieldsBefore(contextField, newField);
+            this.focusPreviousTag(contextField);
+
+        } else {
+            this.record.insertFieldsAfter(contextField, newField);
+            this.focusNextTag(contextField);
+        }
+    }
+
+
+    deleteField(field: MarcField) {
+        this.recordChanging();
+        this.record.deleteFields(field);
+        this.focusNextTag(field) || this.focusPreviousTag(field);
+    }
+
+    deleteSubfield(field: MarcField, subfield: MarcSubfield) {
+        this.recordChanging();
+        // If subfields remain, focus the previous subfield.
+        // otherwise focus our tag.
+        const sfpos = subfield[2] - 1;
+
+        field.deleteExactSubfields(subfield);
+
+        const focus: FieldFocusRequest = {fieldId: field.fieldId, target: 'tag'};
+
+        if (sfpos >= 0) { 
+            focus.target = 'sfv';
+            focus.sfOffset = sfpos; 
+        }
+
+        this.requestFieldFocus(focus);
+    }
+
     // Returns true if the field has a next tag to focus
     focusNextTag(field: MarcField) {
         const nextField = this.record.getNextField(field.fieldId);