--- /dev/null
+
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Physical Characteristics Wizard</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+
+ <div class="form-group row">
+ <label for="007-value"
+ class="col-lg-4 col-form-label" i18n>007 Value</label>
+ <div class="col-lg-5 well-table">
+ <div class="well-value">
+ <!-- avoid newlines and spaces in the <pre> content -->
+ <pre class="pb-0 mb-0 pt-1">{{splitFieldData()[0]}}<span
+ class="text-danger">{{splitFieldData()[1]}}</span>{{splitFieldData()[2]}}</pre>
+ </div>
+ </div>
+ <div class="col-lg-3 d-flex">
+ <button class="btn btn-outline-dark m-1 p-1 flex-1" (click)="reset()" i18n>Reset</button>
+ <button class="btn btn-outline-dark m-1 p-1 flex-1" (click)="reset(true)" i18n>Clear</button>
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <label for="value-selector" class="col-lg-4 col-form-label">
+ <ng-container *ngIf="!selectorLabel" i18n>Category of Material</ng-container>
+ <ng-container *ngIf="selectorLabel">{{selectorLabel}}</ng-container>
+ </label>
+ <div class="col-lg-5">
+ <select id='value-selector' class="form-control"
+ [(ngModel)]="selectorValue" (change)="selectorChanged()">
+ <option value=" " i18n><Unset></option>
+ <option *ngFor="let entry of selectorOptions"
+ [ngValue]="entry.id" i18n>{{entry.id}}: {{entry.label}}</option>
+ </select>
+ </div>
+ <div class="col-lg-3 d-flex">
+ <button type="button" class="btn btn-outline-dark flex-1 m-1 p-1"
+ (click)="prev()" [disabled]="step === 0" i18n>Previous</button>
+
+ <button type="button" class="btn btn-outline-dark flex-1 m-1 p-1"
+ (click)="next()" [disabled]="isLastStep()" i18n>Next</button>
+ </div>
+ </div>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close(fieldData)" i18n>Apply</button>
+
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Cancel</button>
+ </div>
+
+</ng-template>
--- /dev/null
+import {Component, ViewChild, Input, Output, OnInit, EventEmitter} from '@angular/core';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {MarcEditorDialogComponent} from './editor-dialog.component';
+import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+/**
+ * 007 Physical Characteristics Dialog
+ *
+ * Note the dialog does not many direct changes to the bib field.
+ * It simply emits the new value on close, or null of the
+ * dialog canceled.
+ */
+
+@Component({
+ selector: 'eg-phys-char-dialog',
+ templateUrl: './phys-char-dialog.component.html'
+})
+
+export class PhysCharDialogComponent
+ extends DialogComponent implements OnInit {
+
+ // The 007 data
+ @Input() fieldData = '';
+
+ initialValue: string;
+
+ selectorLabel: string = null;
+ selectorValue: string;
+ selectorOptions: ComboboxEntry[] = [];
+
+ typeMap: ComboboxEntry[] = [];
+
+ sfMap: {[ptypeKey: string]: any[]} = {};
+ valueMap: {[ptypeKey: string]: ComboboxEntry[]} = {};
+
+ currentPtype: string;
+
+ // step is the 1-based position in the list of data slots for the
+ // currently selected type. step==0 means we are currently selecting
+ // the type.
+ step = 0;
+
+ // size and offset of the slot we're currently editing; this is
+ // maintained as a convenience for the highlighting of the currently
+ // active position
+ slotOffset = 0;
+ slotSize = 1;
+
+ constructor(
+ private modal: NgbModal,
+ private idl: IdlService,
+ private pcrud: PcrudService) {
+ super(modal);
+ }
+
+ ngOnInit() {
+ this.onOpen$.subscribe(_ => {
+ this.initialValue = this.fieldData;
+ this.reset();
+ });
+ }
+
+ // Chop the field data value into 3 parts, before, middle, and
+ // after, where 'middle' is the part we're currently editing.
+ splitFieldData(): string[] {
+ const data = this.fieldData;
+ return [
+ data.substring(0, this.slotOffset),
+ data.substring(this.slotOffset, this.slotOffset + this.slotSize),
+ data.substring(this.slotOffset + this.slotSize)
+ ];
+ }
+
+ setValuesForStep(): Promise<any> {
+ let promise;
+
+ if (this.step === 0) {
+ promise = this.getPhysCharTypeMap();
+ } else {
+ promise = this.currentSubfield().then(
+ subfield => this.getPhysCharValueMap(subfield.id()));
+ }
+
+ return promise.then(list => {
+ this.selectorOptions = list;
+ this.setSelectedOptionFromField();
+ this.setLabelForStep();
+ });
+ }
+
+ setLabelForStep() {
+ if (this.step === 0) {
+ this.selectorLabel = null; // fall back to template value
+ } else {
+ this.currentSubfield().then(sf => this.selectorLabel = sf.label());
+ }
+ }
+
+ getStepSlot(): Promise<any[]> {
+ if (this.step === 0) { return Promise.resolve([0, 1]); }
+ return this.currentSubfield()
+ .then(sf => [sf.start_pos(), sf.length()]);
+ }
+
+ setSelectedOptionFromField() {
+ this.getStepSlot().then(slot => {
+ this.slotOffset = slot[0];
+ this.slotSize = slot[1];
+ this.selectorValue =
+ String.prototype.substr.apply(this.fieldData, slot) || ' ';
+ });
+ }
+
+ isLastStep(): boolean {
+ // This one is called w/ every digest, so avoid async
+ // calls. Wait until we have loaded the current ptype
+ // subfields to determine if this is the last step.
+ return (
+ this.currentPtype &&
+ this.sfMap[this.currentPtype] &&
+ this.sfMap[this.currentPtype].length === this.step
+ );
+ }
+
+ selectorChanged() {
+
+ if (this.step === 0) {
+ this.currentPtype = this.selectorValue;
+ this.fieldData = this.selectorValue; // total reset
+
+ } else {
+ this.getStepSlot().then(slot => {
+
+ let value = this.fieldData;
+ const offset = slot[0];
+ const size = slot[1];
+ while (value.length < (offset + size)) { value += ' '; }
+
+ // Apply the value to the field in the required slot,
+ // then delete all data after "here", since those values
+ // no longer make sense.
+ const before = value.substr(0, offset);
+ this.fieldData = before + this.selectorValue.padEnd(size, ' ');
+ this.slotOffset = offset;
+ this.slotSize = size;
+ });
+ }
+ }
+
+ next() {
+ this.step++;
+ this.setValuesForStep();
+ }
+
+ prev() {
+ this.step--;
+ this.setValuesForStep();
+ }
+
+ currentSubfield(): Promise<any> {
+ return this.getPhysCharSubfieldMap(this.currentPtype)
+ .then(sfList => sfList[this.step - 1]);
+ }
+
+ reset(clear?: boolean) {
+ this.step = 0;
+ this.slotOffset = 0;
+ this.slotSize = 1;
+ this.fieldData = clear ? ' ' : this.initialValue;
+ this.currentPtype = this.fieldData.substr(0, 1);
+ this.setValuesForStep();
+ }
+
+ getPhysCharTypeMap(): Promise<ComboboxEntry[]> {
+ if (this.typeMap.length) {
+ return Promise.resolve(this.typeMap);
+ }
+
+ return this.pcrud.retrieveAll(
+ 'cmpctm', {order_by: {cmpctm: 'label'}}, {atomic: true})
+ .toPromise().then(maps => {
+ return this.typeMap = maps.map(
+ map => ({id: map.ptype_key(), label: map.label()}));
+ });
+ }
+
+ getPhysCharSubfieldMap(ptypeKey: string): Promise<IdlObject[]> {
+
+ if (this.sfMap[ptypeKey]) {
+ return Promise.resolve(this.sfMap[ptypeKey]);
+ }
+
+ return this.pcrud.search('cmpcsm',
+ {ptype_key : ptypeKey},
+ {order_by : {cmpcsm : ['start_pos']}},
+ {atomic : true}
+ ).toPromise().then(maps => this.sfMap[ptypeKey] = maps);
+ }
+
+ getPhysCharValueMap(ptypeSubfield: string): Promise<ComboboxEntry[]> {
+
+ if (this.valueMap[ptypeSubfield]) {
+ return Promise.resolve(this.valueMap[ptypeSubfield]);
+ }
+
+ return this.pcrud.search('cmpcvm',
+ {ptype_subfield : ptypeSubfield},
+ {order_by : {cmpcsm : ['value']}},
+ {atomic : true}
+ ).toPromise().then(maps =>
+ this.valueMap[ptypeSubfield] = maps.map(
+ map => ({id: map.value(), label: map.label()}))
+ );
+ }
+}
+
+
import {MarcRecord, MarcField} from './marcrecord';
import {MarcEditContext} from './editor-context';
import {AuthorityLinkingDialogComponent} from './authority-linking-dialog.component';
+import {PhysCharDialogComponent} from './phys-char-dialog.component';
/**
@ViewChild('authLinker', {static: false})
authLinker: AuthorityLinkingDialogComponent;
+ @ViewChild('physCharDialog', {static: false})
+ physCharDialog: PhysCharDialogComponent;
+
constructor(
private idl: IdlService,
private net: NetService,
openLinkerDialog(field: MarcField) {
this.authLinker.bibField = field;
this.authLinker.open({size: 'xl'}).subscribe(newField => {
+
+ // The presence of newField here means the linker wants to
+ // replace the field with a new field from the authority
+ // record. Otherwise, the original field may have been
+ // directly modified or the dialog canceled.
if (!newField) { return; }
// Performs an insert followed by a delete, so the two
this.context.setUndoGroupSize(2);
});
}
+
+ // 007 Physical characteristics wizard.
+ openPhysCharDialog(field: MarcField) {
+ this.physCharDialog.fieldData = field.data;
+
+ this.physCharDialog.open({size: 'lg'}).subscribe(
+ newData => {
+ if (newData) {
+ this.context.requestFieldFocus({
+ fieldId: field.fieldId,
+ target: 'cfld',
+ newText: newData
+ });
+ }
+ }
+ );
+ }
}