From 50d94ef3fd4951b292d6c3a49e494f0a295ff381 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 9 Dec 2019 16:41:09 -0500 Subject: [PATCH] LPXXX move fixed fields to editable-content Signed-off-by: Bill Erickson --- .../share/marc-edit/editable-content.component.ts | 81 +++++++++++++++++++--- .../app/staff/share/marc-edit/editor-context.ts | 7 +- .../share/marc-edit/fixed-field.component.html | 17 ++--- .../staff/share/marc-edit/fixed-field.component.ts | 59 +++------------- .../share/marc-edit/rich-editor.component.html | 17 ++++- .../staff/share/marc-edit/rich-editor.component.ts | 20 +++++- .../app/staff/share/marc-edit/tagtable.service.ts | 67 ++++++++++++++---- 7 files changed, 177 insertions(+), 91 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts index 72d432c85f..5f4b1ea8ab 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts @@ -31,6 +31,8 @@ export class EditableContentComponent // array of subfield code and subfield value @Input() subfield: MarcSubfield; + @Input() fixedFieldCode: string; + // space-separated list of additional CSS classes to append @Input() moreClasses: string; @@ -49,6 +51,14 @@ export class EditableContentComponent undoRedoSub: Subscription; isLeader: boolean; // convenience + // Cache of fixed field menu options + ffValues: ContextMenuEntry[] = []; + + // Track the fixed field value locally since extracting the value + // in real time from the record, which adds padding to the text, + // causes usability problems. + ffValue: string; + constructor( private renderer: Renderer2, private tagTable: TagTableService) {} @@ -81,6 +91,9 @@ export class EditableContentComponent if (req.fieldId !== this.field.fieldId) { return false; } } else if (req.target === 'ldr') { return this.isLeader; + } else if (req.target === 'ffld' && + req.ffCode !== this.fixedFieldCode) { + return false; } if (req.sfOffset !== undefined && @@ -106,7 +119,8 @@ export class EditableContentComponent req = { fieldId: this.field ? this.field.fieldId : -1, target: this.fieldType, - sfOffset: this.subfield ? this.subfield[2] : undefined + sfOffset: this.subfield ? this.subfield[2] : undefined, + ffCode: this.fixedFieldCode }; } @@ -136,6 +150,12 @@ export class EditableContentComponent this.watchForUndoRedoRequests(); break; + case 'ffld': + this.applyFFOptions(); + this.watchForFocusRequests(); + this.watchForUndoRedoRequests(); + break; + case 'ind1': case 'ind2': this.maxLength = 1; @@ -157,6 +177,19 @@ export class EditableContentComponent } } + applyFFOptions() { + return this.tagTable.getFfFieldMeta( + this.fixedFieldCode, this.record.recordType()) + .then(fieldMeta => { + if (fieldMeta) { + this.maxLength = fieldMeta.length || 1; + } + }); + + // Fixed field options change when the record type changes. + this.context.recordChange.subscribe(_ => this.applyFFOptions()); + } + // These are served dynamically to handle cases where a tag or // subfield is modified in place. contextMenuEntries(): ContextMenuEntry[] { @@ -177,6 +210,10 @@ export class EditableContentComponent case 'ind2': return this.tagTable.getIndicatorValues( this.field.tag, this.fieldType); + + case 'ffld': + return this.tagTable.getFfValues( + this.fixedFieldCode, this.record.recordType()); } return null; @@ -191,9 +228,28 @@ export class EditableContentComponent case 'tag': return this.field.tag; case 'sfc': return this.subfield[0]; case 'sfv': return this.subfield[1]; - case 'ind1': // thunk - case 'ind2': return this.field[this.fieldType]; + case 'ind1': return this.field.ind1; + case 'ind2': return this.field.ind2; + + case 'ffld': + // When actively editing a fixed field, track its value + // in a local variable instead of pulling the value + // from record.extractFixedField(), which applies + // additional formattting, causing usability problems + // (e.g. unexpected spaces). Once focus is gone, the + // view will be updated with the correctly formatted + // value. + + if ( this.ffValue === undefined || + !this.context.lastFocused || + !this.focusRequestIsMe(this.context.lastFocused)) { + + this.ffValue = + this.record.extractFixedField(this.fixedFieldCode); + } + return this.ffValue; } + return 'X'; } setContent(value: string, propagatBigText?: boolean, skipUndoTrack?: boolean) { @@ -206,8 +262,13 @@ export class EditableContentComponent case 'tag': this.field.tag = value; break; case 'sfc': this.subfield[0] = value; break; case 'sfv': this.subfield[1] = value; break; - case 'ind1': // thunk - case 'ind2': this.field[this.fieldType] = value; break; + case 'ind1': this.field.ind1 = value; break; + case 'ind2': this.field.ind2 = value; break; + case 'ffld': + // Track locally and propagate to the record. + this.ffValue = value; + this.record.setFixedField(this.fixedFieldCode, value); + break; } if (propagatBigText && this.bigText) { @@ -224,6 +285,9 @@ export class EditableContentComponent trackTextChangeForUndo(value: string) { + // Human-driven changes invalidate the redo stack. + this.context.redoStack = []; + const lastUndo = this.context.undoStack[0]; if (lastUndo @@ -280,7 +344,7 @@ export class EditableContentComponent inputSize(): number { if (this.maxLength) { - return this.maxLength; + return this.maxLength + 1; } // give at least 2+ chars space and grow with the content return Math.max(2, (this.getContent() || '').length) * 1.1; @@ -349,8 +413,9 @@ export class EditableContentComponent return; } - // None of the remaining key combos are supported by the LDR. - if (this.fieldType === 'ldr') { return; } + // None of the remaining key combos are supported by the LDR + // or fixed field editor. + if (this.fieldType === 'ldr' || this.fieldType === 'ffld') { return; } switch (evt.key) { diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts index 4e15a9afac..1e976c8c65 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts @@ -7,12 +7,13 @@ import {NgbPopover} from '@ng-bootstrap/ng-bootstrap'; const STUB_DATA_00X = ' '; export type MARC_EDITABLE_FIELD_TYPE = - 'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv'; + 'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv' | 'ffld'; export interface FieldFocusRequest { fieldId: number; target: MARC_EDITABLE_FIELD_TYPE; sfOffset?: number; // focus a specific subfield by its offset + ffCode?: string; // fixed field code } export class UndoRedoAction { @@ -91,6 +92,7 @@ export class MarcEditContext { } requestUndo() { + console.debug('undo requested with stack size ', this.undoStack.length); const undo = this.undoStack.shift(); if (undo) { undo.isRedo = false; @@ -176,6 +178,9 @@ export class MarcEditContext { trackStructuralUndo(field: MarcField, isAddition: boolean, subfield?: MarcSubfield) { + // Human-driven changes invalidate the redo stack so clear it. + this.redoStack = []; + const position: FieldFocusRequest = {fieldId: field.fieldId, target: 'tag'}; let prevPos: FieldFocusRequest = null; diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.html b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.html index 856e56da4f..e2e8976197 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.html @@ -1,21 +1,16 @@ -
+
{{fieldLabel}}
- +
+ + +
- diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.ts b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.ts index 703aaea61f..a81255dc6d 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/fixed-field.component.ts @@ -1,7 +1,8 @@ import {Component, Input, Output, OnInit, EventEmitter} from '@angular/core'; +import {IdlObject} from '@eg/core/idl.service'; import {MarcRecord} from './marcrecord'; import {MarcEditContext} from './editor-context'; -import {TagTableService, ValueLabelPair} from './tagtable.service'; +import {TagTableService} from './tagtable.service'; /** * MARC Fixed Field Editing Component @@ -21,10 +22,7 @@ export class FixedFieldComponent implements OnInit { get record(): MarcRecord { return this.context.record; } - fieldValue: string; - fieldMeta: any; - fieldLength: number = null; - fieldValues: ValueLabelPair[] = null; + fieldMeta: IdlObject; randId = Math.floor(Math.random() * 10000000); constructor(private tagTable: TagTableService) {} @@ -32,57 +30,16 @@ export class FixedFieldComponent implements OnInit { ngOnInit() { this.init().then(_ => this.context.recordChange.subscribe(__ => this.init())); - } init(): Promise { if (!this.record) { return Promise.resolve(); } - this.fieldValues = null; - return this.tagTable.getFFPosTable(this.record.recordType()) - .then(table => { - - // Note the AngJS MARC editor stores the full POS table - // for all record types in every copy of the table, hence - // the seemingly extraneous check in recordType. - this.fieldMeta = table.filter(field => - field.fixed_field === this.fieldCode - && field.rec_type === this.record.recordType())[0]; - - if (!this.fieldMeta) { - // Not all record types have all field types. - return; - } - - this.fieldLength = this.fieldMeta.length || 1; - this.fieldValue = - this.context.record.extractFixedField(this.fieldCode); - - // Shuffling may occur with our fixed field as a result of - // external changes. - this.record.fixedFieldChange.subscribe(_ => - this.fieldValue = - this.context.record.extractFixedField(this.fieldCode) - ); - - return this.tagTable.getFFValueTable(this.record.recordType()); - - }).then(values => { - if (!values || !values[this.fieldCode]) { return; } - - // extract the canned set of possible values for our - // fixed field. Ignore those whose value exceeds the - // specified field length. - this.fieldValues = values[this.fieldCode] - .filter(val => val[0].length <= val[2]) - .map(val => ({value: val[0], label: `${val[0]}: ${val[1]}`})) - .sort((a, b) => a.label < b.label ? -1 : 1); - }); - } - - valueChange(newVal: string) { - this.fieldValue = newVal; - this.context.record.setFixedField(this.fieldCode, this.fieldValue); + // If no field metadata is found for this fixed field code and + // record type combo, the field will be hidden in the UI. + return this.tagTable.getFfFieldMeta( + this.fieldCode, this.record.recordType()) + .then(fieldMeta => this.fieldMeta = fieldMeta); } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.html b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.html index 5f32ef619b..cf214424d6 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.html @@ -21,6 +21,16 @@ [disabled]="true" (click)="validate()" i18n>Validate
+ + +
+
+
+
-
    @@ -63,11 +74,11 @@
    + fieldText="LDR" i18n-fieldText moreClasses="p-1"> + moreClasses="p-1 pr-2">
    diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.ts b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.ts index aa03c20c53..1c50c57c4f 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.ts @@ -45,11 +45,27 @@ export class MarcRichEditorComponent implements OnInit { return Promise.all([ this.tagTable.loadTagTable({marcRecordType: this.context.recordType}), - this.tagTable.getFFPosTable(this.record.recordType()), - this.tagTable.getFFValueTable(this.record.recordType()) + this.tagTable.getFfPosTable(this.record.recordType()), + this.tagTable.getFfValueTable(this.record.recordType()) ]).then(_ => this.dataLoaded = true); } + undoCount(): number { + return this.context.undoStack.length; + } + + redoCount(): number { + return this.context.redoStack.length; + } + + undo() { + this.context.requestUndo(); + } + + redo() { + this.context.requestRedo(); + } + controlFields(): MarcField[] { return this.record.fields.filter(f => f.isControlfield()); } diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/tagtable.service.ts b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/tagtable.service.ts index 8e206c8e3e..ec5abd04b3 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/tagtable.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/tagtable.service.ts @@ -1,15 +1,12 @@ import {Injectable, EventEmitter} from '@angular/core'; import {map, tap} from 'rxjs/operators'; import {StoreService} from '@eg/core/store.service'; +import {IdlObject} from '@eg/core/idl.service'; import {AuthService} from '@eg/core/auth.service'; import {NetService} from '@eg/core/net.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {EventService} from '@eg/core/event.service'; - -export interface ValueLabelPair { - value: string; - label: string; -} +import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service'; interface TagTableSelector { marcFormat?: string; @@ -44,13 +41,14 @@ export class TagTableService { fieldtags: {}, indicators: {}, sfcodes: {}, - sfvalues: {} + sfvalues: {}, + ffvalues: {} }; } // Various data needs munging for display. Cached the modified // values since they are refernced repeatedly by the UI code. - fromCache(dataType: string, which?: string, which2?: string): ValueLabelPair[] { + fromCache(dataType: string, which?: string, which2?: string): ContextMenuEntry[] { const part1 = this.extractedValuesCache[dataType][which]; if (which2) { if (part1) { @@ -62,7 +60,7 @@ export class TagTableService { } toCache(dataType: string, which: string, - which2: string, values: ValueLabelPair[]): ValueLabelPair[] { + which2: string, values: ContextMenuEntry[]): ContextMenuEntry[] { const base = this.extractedValuesCache[dataType]; const part1 = base[which]; @@ -76,7 +74,7 @@ export class TagTableService { return values; } - getFFPosTable(rtype: string): Promise { + getFfPosTable(rtype: string): Promise { const storeKey = 'FFPosTable_' + rtype; if (this.ffPosMap[rtype]) { @@ -99,7 +97,7 @@ export class TagTableService { }); } - getFFValueTable(rtype: string): Promise { + getFfValueTable(rtype: string): Promise { const storeKey = 'FFValueTable_' + rtype; @@ -162,7 +160,7 @@ export class TagTableService { })).toPromise(); } - getSubfieldCodes(tag: string): ValueLabelPair[] { + getSubfieldCodes(tag: string): ContextMenuEntry[] { if (!tag || !this.tagMap[tag]) { return null; } const cached = this.fromCache('sfcodes', tag); @@ -176,7 +174,7 @@ export class TagTableService { return this.toCache('sfcodes', tag, null, list); } - getFieldTags(): ValueLabelPair[] { + getFieldTags(): ContextMenuEntry[] { const cached = this.fromCache('fieldtags'); if (cached) { return cached; } @@ -190,13 +188,13 @@ export class TagTableService { .sort((a, b) => a.label < b.label ? -1 : 1); } - getSubfieldValues(tag: string, sfCode: string): ValueLabelPair[] { + getSubfieldValues(tag: string, sfCode: string): ContextMenuEntry[] { if (!tag || !this.tagMap[tag]) { return []; } const cached = this.fromCache('sfvalues', tag, sfCode) if (cached) { return cached; } - const list: ValueLabelPair[] = []; + const list: ContextMenuEntry[] = []; this.tagMap[tag].subfields .filter(sf => @@ -215,7 +213,7 @@ export class TagTableService { return this.toCache('sfvalues', tag, sfCode, list); } - getIndicatorValues(tag: string, which: 'ind1' | 'ind2'): ValueLabelPair[] { + getIndicatorValues(tag: string, which: 'ind1' | 'ind2'): ContextMenuEntry[] { if (!tag || !this.tagMap[tag]) { return } const cached = this.fromCache('indicators', tag, which); @@ -232,6 +230,45 @@ export class TagTableService { return this.toCache('indicators', tag, which, values); } + + + getFfFieldMeta(fieldCode: string, recordType: string): Promise { + return this.getFfPosTable(recordType).then(table => { + + // Note the AngJS MARC editor stores the full POS table + // for all record types in every copy of the table, hence + // the seemingly extraneous check in recordType. + return table.filter( + field => + field.fixed_field === fieldCode + && field.rec_type === recordType + )[0]; + }); + } + + + // Assumes getFfPosTable and getFfValueTable have already been + // invoked for the request record type. + getFfValues(fieldCode: string, recordType: string): ContextMenuEntry[] { + + const cached = this.fromCache('ffvalues', recordType, fieldCode); + if (cached) { return cached; } + + let values = this.ffValueMap[recordType]; + + if (!values || !values[fieldCode]) { return null; } + + // extract the canned set of possible values for our + // fixed field. Ignore those whose value exceeds the + // specified field length. + values = values[fieldCode] + .filter(val => val[0].length <= val[2]) + .map(val => ({value: val[0], label: `${val[0]}: ${val[1]}`})) + .sort((a, b) => a.label < b.label ? -1 : 1); + + return this.toCache('ffvalues', recordType, fieldCode, values); + } } + -- 2.11.0