1 import {Component, OnInit, Input, ViewChild} from '@angular/core';
2 import {Observable, throwError, from, empty} from 'rxjs';
3 import {tap, map, switchMap} from 'rxjs/operators';
4 import {NetService} from '@eg/core/net.service';
5 import {IdlService, IdlObject} from '@eg/core/idl.service';
6 import {EventService} from '@eg/core/event.service';
7 import {ToastService} from '@eg/share/toast/toast.service';
8 import {AuthService} from '@eg/core/auth.service';
9 import {PcrudService} from '@eg/core/pcrud.service';
10 import {OrgService} from '@eg/core/org.service';
11 import {StringComponent} from '@eg/share/string/string.component';
12 import {DialogComponent} from '@eg/share/dialog/dialog.component';
13 import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
14 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
17 * Dialog for managing copy tags.
21 selector: 'eg-copy-tags-dialog',
22 templateUrl: 'copy-tags-dialog.component.html'
25 export class CopyTagsDialogComponent
26 extends DialogComponent implements OnInit {
28 // If there are multiple copyIds, only new tags may be applied.
29 // If there is only one copyId, then tags may be applied or removed.
30 @Input() copyIds: number[] = [];
32 mode: string; // create | manage
34 // If true, no attempt is made to save the new tags to the
35 // database. It's assumed this takes place in the calling code.
36 // This is useful for creating tags for new copies.
37 @Input() inPlaceMode = false;
39 // In 'create' mode, we may be adding notes to multiple copies.
40 copies: IdlObject[] = [];
42 // In 'manage' mode we only handle a single copy.
45 tagTypes: ComboboxEntry[];
47 curTag: ComboboxEntry = null;
48 curTagType: ComboboxEntry = null;
49 newTags: IdlObject[] = [];
50 deletedMaps: IdlObject[] = [];
51 tagMap: {[id: number]: IdlObject} = {};
52 tagTypeMap: {[id: number]: IdlObject} = {};
54 tagDataSource: (term: string) => Observable<ComboboxEntry>;
56 @ViewChild('successMsg', { static: true }) private successMsg: StringComponent;
57 @ViewChild('errorMsg', { static: true }) private errorMsg: StringComponent;
60 private modal: NgbModal, // required for passing to parent
61 private toast: ToastService,
62 private net: NetService,
63 private idl: IdlService,
64 private pcrud: PcrudService,
65 private org: OrgService,
66 private auth: AuthService) {
67 super(modal); // required for subclassing
72 this.tagDataSource = term => {
73 if (!this.curTagType) { return empty(); }
75 return this.pcrud.search(
77 tag_type: this.curTagType.id,
79 {value: {'ilike': `%${term}%`}},
80 {label: {'ilike': `%${term}%`}}
83 {order_by: {acpt: 'label'}}
84 ).pipe(map(copyTag => {
85 this.tagMap[copyTag.id()] = copyTag;
86 return {id: copyTag.id(), label: copyTag.label()};
93 open(args: NgbModalOptions): Observable<IdlObject[]> {
97 this.deletedMaps = [];
99 if (this.copyIds.length === 0 && !this.inPlaceMode) {
100 return throwError('copy ID required');
103 // In manage mode, we can only manage a single copy.
104 // But in create mode, we can add tags to multiple copies.
106 if (this.copyIds.length === 1 && !this.inPlaceMode) {
107 this.mode = 'manage';
109 this.mode = 'create';
112 // Observify data loading
113 const obs = from(this.getTagTypes().then(_ => this.getCopies()));
115 // Return open() observable to caller
116 return obs.pipe(switchMap(_ => super.open(args)));
119 getTagTypes(): Promise<any> {
120 if (this.tagTypes) { return Promise.resolve(); }
123 return this.pcrud.search('cctt',
124 {owner: this.org.ancestors(this.auth.user().ws_ou(), true)},
125 {order_by: {cctt: 'label'}}
127 this.tagTypeMap[tag.code()] = tag;
128 this.tagTypes.push({id: tag.code(), label: tag.label()});
132 getCopies(): Promise<any> {
133 if (this.inPlaceMode) { return Promise.resolve(); }
135 return this.pcrud.search('acp', {id: this.copyIds},
136 {flesh: 3, flesh_fields: {
137 acp: ['tags'], acptcm: ['tag'], acpt: ['tag_type']}},
140 .toPromise().then(copies => {
141 this.copies = copies;
142 if (copies.length === 1) {
143 this.copy = copies[0];
148 removeTag(tag: IdlObject) {
149 this.newTags = this.newTags.filter(t => t.id() !== tag.id());
151 if (tag.isnew() || this.mode === 'create') { return; }
153 const existing = this.copy.tags().filter(m => m.tag().id() === tag.id())[0];
154 if (!existing) { return; }
156 existing.isdeleted(true);
157 this.deletedMaps.push(existing);
158 this.copy.tags(this.copy.tags().filter(m => m.tag().id() !== tag.id()));
159 this.copy.ischanged(true);
163 if (!this.curTagType || !this.curTag) { return; }
167 if (this.curTag.freetext) {
168 // Create a new tag w/ the provided tag text.
169 tag = this.idl.create('acpt');
171 tag.tag_type(this.curTagType.id);
172 tag.label(this.curTag.label);
173 tag.owner(this.auth.user().ws_ou());
176 tag = this.tagMap[this.curTag.id];
179 this.newTags.push(tag);
182 createNewTags(): Promise<any> {
183 let promise = Promise.resolve();
185 this.newTags.forEach(tag => {
186 if (!tag.isnew()) { return; }
188 promise = promise.then(_ => {
189 return this.pcrud.create(tag).toPromise().then(id => {
190 console.log('create returned ', id);
199 deleteMaps(): Promise<any> {
200 if (this.deletedMaps.length === 0) { return Promise.resolve(); }
201 return this.pcrud.remove(this.deletedMaps).toPromise();
206 if (this.inPlaceMode) {
207 this.close(this.newTags);
211 let promise = this.deleteMaps().then(_ => this.createNewTags());
213 this.newTags.forEach(tag => {
214 this.copies.forEach(copy => {
216 if (copy.tags() && copy.tags().filter(
217 m => m.tag().id() === tag.id()).length > 0) {
218 return; // map already exists
221 promise = promise.then(_ => {
222 const tagMap = this.idl.create('acptcm');
224 tagMap.copy(copy.id());
225 tagMap.tag(tag.id());
226 return this.pcrud.create(tagMap).toPromise();
232 this.successMsg.current().then(msg => this.toast.success(msg));
233 this.close(this.newTags.length > 0);