LPXXX undo/redo
authorBill Erickson <berickxx@gmail.com>
Wed, 4 Dec 2019 19:36:32 +0000 (14:36 -0500)
committerBill Erickson <berickxx@gmail.com>
Fri, 6 Dec 2019 15:37:04 +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.ts
Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts

index 0005a78..96d71ac 100644 (file)
@@ -391,7 +391,7 @@ export class EditableContentComponent
                 if (evt.ctrlKey) {
                     // ctrl+i / ctrl+d == insert subfield
                     const pos = this.subfield ? this.subfield[2] + 1 : 0;
-                    this.context.insertSubfield(this.field, pos);
+                    this.context.insertStubSubfield(this.field, pos);
                     evt.preventDefault();
                 }
                 break;
index 90448cb..0e48cb7 100644 (file)
@@ -4,6 +4,8 @@ import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
 
 /* Per-instance MARC editor context. */
 
+const STUB_DATA_00X = '                                        ';
+
 export type MARC_EDITABLE_FIELD_TYPE = 
     'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv';
 
@@ -27,12 +29,18 @@ export interface UndoRedoAction {
     // recovery can extract what's needed.
     field?: MarcField;
 
+    // If this is a subfield modification.
+    subfield?: MarcSubfield;
+
     // Does this action track an addition or deletion.
     wasAddition?: boolean;
 
-    // Reference to position just before the modified position
-    // in the record, so deletion recovery can correctly position.
-    precedingPosition?: FieldFocusRequest;
+    // Position preceding the modified position to mark the position
+    // of deletion recovery.
+    prevPosition?: FieldFocusRequest;
+
+    // Where should focus be returned once the undo is processed?
+    prevFocus?: FieldFocusRequest;
 }
 
 export class MarcEditContext {
@@ -70,6 +78,7 @@ export class MarcEditContext {
         // timeout allows for new components to be built before the
         // focus request is emitted.
         setTimeout(() => {
+            console.log('focusing ', req);
             this.fieldFocusRequest.emit(req);
         });
     }
@@ -100,99 +109,121 @@ export class MarcEditContext {
         }
     }
 
-    handleStructuralUndoRedo(action: UndoRedoAction, isRedo?: boolean) {
+    handleStructuralUndoRedo(action: UndoRedoAction) {
 
         if (action.wasAddition) {
-            this.record.deleteFields(action.field);
-            this.requestFieldFocus(action.precedingPosition);
+            // Remove the added field and focus the field before it.
 
-        } else {
-            const fieldId = action.field.fieldId;
-            const prevField = 
-                this.record.getField(action.precedingPosition.fieldId);
+            if (action.subfield) {
 
-            this.record.insertFieldsAfter(prevField, action.field);
-            
-            // Recover the original fieldId, which gets re-stamped
-            // in this.record.insertFields* calls.
-            action.field.fieldId = fieldId;
+                const prevPos = action.subfield[2] - 1;
+                action.field.deleteExactSubfields(action.subfield);
+                this.focusSubfield(action.field, prevPos);
+
+            } else {
+                this.record.deleteFields(action.field);
+                this.requestFieldFocus(action.prevPosition);
+            }
+
+        } else {
+            // Re-insert the removed field and focus it
             
-            // Focus the newly recovered field.
-            this.requestFieldFocus(action.position);
+            if (action.subfield) { 
+
+                this.insertSubfield(action.field, action.subfield, true);
+                this.focusSubfield(action.field, action.subfield[2]);
+
+            } else {
+                
+                const fieldId = action.position.fieldId;
+                const prevField = 
+                    this.record.getField(action.prevPosition.fieldId);
+
+                this.record.insertFieldsAfter(prevField, action.field);
+                
+                // Recover the original fieldId, which gets re-stamped
+                // in this.record.insertFields* calls.
+                action.field.fieldId = fieldId;
+                
+                // Focus the newly recovered field.
+                this.requestFieldFocus(action.position);
+            }
         }
 
         action.wasAddition = !action.wasAddition;
 
-        const moveTo = isRedo ? this.undoStack : this.redoStack;
+        const moveTo = action.isRedo ? this.undoStack : this.redoStack;
 
         moveTo.unshift(action);
     }
 
-    add00X(tag: string) {
+    trackStructuralUndo(field: MarcField, isAddition: boolean, subfield?: MarcSubfield) {
 
-        const field: MarcField = this.record.newField({
-            tag : tag,
-            data : '                                        '
-        });
+        const position: FieldFocusRequest = {fieldId: field.fieldId, target: 'tag'};
 
-        this.record.insertOrderedFields(field);
+        let prevPos: FieldFocusRequest = null;
 
-        const focus: FieldFocusRequest = 
-            {fieldId: field.fieldId, target: 'tag'};
+        if (subfield) {
+            position.target = 'sfc';
+            position.sfOffset = subfield[2];
 
-        const prevField = this.record.getPreviousField(field.fieldId);
+        } else {
+            // No need to track the previous field for subfield mods.
 
-        let prevFocus: FieldFocusRequest;
-        if (prevField) {
-            prevFocus = {fieldId: prevField.fieldId, target: 'tag'};
+            const prevField = this.record.getPreviousField(field.fieldId);
+            if (prevField) {
+                prevPos = {fieldId: prevField.fieldId, target: 'tag'};
+            }
         }
 
-        this.undoStack.unshift({
-            wasAddition: true,
+        const action = {
             field: field,
-            position: focus,
-            precedingPosition: prevFocus
-        });
+            subfield: subfield,
+            wasAddition: isAddition,
+            position: position,
+            prevPosition: prevPos,
+            prevFocus: this.lastFocused
+        };
+
+        this.undoStack.unshift(action);
+    }
 
-        this.requestFieldFocus(focus);
+    deleteField(field: MarcField) { 
+        this.trackStructuralUndo(field, false);
+
+        this.focusNextTag(field) || this.focusPreviousTag(field);
+
+        this.record.deleteFields(field);
+    }
+
+    add00X(tag: string) {
+
+        const field: MarcField = 
+            this.record.newField({tag : tag, data : STUB_DATA_00X});
+
+        this.record.insertOrderedFields(field);
+
+        this.trackStructuralUndo(field, true);
+
+        this.focusTag(field);
     }
 
     insertReplace008() {
-        //this.recordChanging();
 
         // delete all of the 008s
-        [].concat(this.record.field('008', true)).forEach(
-            f => this.record.deleteFields(f));
-
-        // add a new 008
-        this.record.insertOrderedFields(
-            this.record.newField({
-                tag : '008',
-                data : this.record.generate008()
-            })
-        );
-    }
+        [].concat(this.record.field('008', true)).forEach(f => {
+            this.trackStructuralUndo(f, false);
+            this.record.deleteFields(f);
+        });
 
-    // Adds a new empty subfield to the provided field at the
-    // requested subfield position
-    insertSubfield(field: MarcField, position: number) {
-        //this.recordChanging();
+        const field = this.record.newField({
+            tag : '008', data : this.record.generate008()});
 
-        // array index 3 contains that position of the subfield
-        // in the MARC field.  When splicing a new subfield into
-        // the set, be sure the any that come after the new one
-        // have their positions bumped to reflect the shift.
-        field.subfields.forEach(
-            sf => {if (sf[2] >= position) { sf[2]++; }});
+        this.record.insertOrderedFields(field);
 
-        const newSf: MarcSubfield = [' ', '', position];
-        field.subfields.splice(position, 0, newSf);
+        this.trackStructuralUndo(field, true);
 
-        this.requestFieldFocus({
-            fieldId: field.fieldId, 
-            target: 'sfc',
-            sfOffset: position
-        });
+        this.focusTag(field);
     }
 
     // Add stub field before or after the context field
@@ -205,7 +236,6 @@ export class MarcEditContext {
     }
 
     insertField(contextField: MarcField, newField: MarcField, before?: boolean) {
-        //this.recordChanging();
 
         if (before) {
             this.record.insertFieldsBefore(contextField, newField);
@@ -215,39 +245,71 @@ export class MarcEditContext {
             this.record.insertFieldsAfter(contextField, newField);
             this.focusNextTag(contextField);
         }
+
+        this.trackStructuralUndo(newField, true);
     }
 
+    // Adds a new empty subfield to the provided field at the
+    // requested subfield position
+    insertSubfield(field: MarcField, 
+        subfield: MarcSubfield, skipTracking?: boolean) {
+        const position = subfield[2];
 
-    deleteField(field: MarcField) {
-        //this.recordChanging();
-        this.record.deleteFields(field);
-        this.focusNextTag(field) || this.focusPreviousTag(field);
-    }
+        // array index 3 contains that position of the subfield
+        // in the MARC field.  When splicing a new subfield into
+        // the set, be sure the any that come after the new one
+        // have their positions bumped to reflect the shift.
+        field.subfields.forEach(
+            sf => {if (sf[2] >= position) { sf[2]++; }});
 
-    deleteSubfield(field: MarcField, subfield: MarcSubfield) {
-        //this.recordChanging();
-        // If subfields remain, focus the previous subfield.
-        // otherwise focus our tag.
-        const sfpos = subfield[2] - 1;
+        field.subfields.splice(position, 0, subfield);
 
-        field.deleteExactSubfields(subfield);
+        if (!skipTracking) {
+            this.focusSubfield(field, position);
+            this.trackStructuralUndo(field, true, subfield);
+        }
+    }
+
+    insertStubSubfield(field: MarcField, position: number) {
+        const newSf: MarcSubfield = [' ', '', position];
+        this.insertSubfield(field, newSf);
+    }
+    
+    // Focus the requested subfield by its position.  If its 
+    // position is less than zero, focus the field's tag instead.
+    focusSubfield(field: MarcField, position: number) {
 
         const focus: FieldFocusRequest = {fieldId: field.fieldId, target: 'tag'};
 
-        if (sfpos >= 0) { 
-            focus.target = 'sfv';
-            focus.sfOffset = sfpos; 
+        if (position >= 0) { 
+            // Focus the code instead of the value, because attempting to
+            // focus an empty (editable) div results in nothing getting focus.
+            focus.target = 'sfc';
+            focus.sfOffset = position; 
         }
 
         this.requestFieldFocus(focus);
     }
 
+    deleteSubfield(field: MarcField, subfield: MarcSubfield) {
+        const sfpos = subfield[2] - 1; // previous subfield
+
+        this.trackStructuralUndo(field, false, subfield);
+
+        field.deleteExactSubfields(subfield);
+
+        this.focusSubfield(field, sfpos);
+    }
+
+    focusTag(field: MarcField) {
+        this.requestFieldFocus({fieldId: field.fieldId, target: 'tag'});
+    }
+
     // Returns true if the field has a next tag to focus
     focusNextTag(field: MarcField) {
         const nextField = this.record.getNextField(field.fieldId);
-        if (nextField) {
-            this.requestFieldFocus(
-                {fieldId: nextField.fieldId, target: 'tag'});
+        if (nextField) { 
+            this.focusTag(nextField); 
             return true;
         }
         return false;
@@ -257,13 +319,10 @@ export class MarcEditContext {
     focusPreviousTag(field: MarcField): boolean {
         const prevField = this.record.getPreviousField(field.fieldId);
         if (prevField) {
-            this.requestFieldFocus(
-                {fieldId: prevField.fieldId, target: 'tag'});
+            this.focusTag(prevField);
             return true;
         }
         return false;
     }
-
-
 }