-
Notifications
You must be signed in to change notification settings - Fork 265
/
bernie.page.ts
207 lines (179 loc) · 9.16 KB
/
bernie.page.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { Store } from '@ngrx/store';
import { SortablejsOptions } from 'angular-sortablejs';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Router, ActivatedRoute } from '@angular/router';
import * as fromRoot from '../../core/store';
import { BerniePageLayout } from './bernie.layout';
import { Claim, ClaimFields, initialClaim } from '../../core/store/claim/claim.model';
import { Rebuttal, RebuttalFields } from '../../core/store/rebuttal/rebuttal.model';
import { ClaimRebuttal } from '../../core/store/claim-rebuttal/claim-rebuttal.model';
import { Entities } from '../../core/store/entity/entity.model';
import * as EntityActions from '../../core/store/entity/entity.actions';
import * as SliceActions from '../../core/store/slice/slice.actions';
import { slices, handleNavigation } from '../../core/store/util';
@Component({
selector: 'jhi-bernie',
templateUrl: './bernie.page.html',
styleUrls: ['./bernie.page.scss'],
// This is inappropriate here because this component has internal state
// For every component you have to choose between either using 1) Observables and the async pipe or using 2) internal state
// If you use internal state, then you should not use ChangeDetectionStrategy.OnPush which is an optimization
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BerniePage implements OnInit, OnDestroy {
// Subscriptions
pageSub: Subscription;
claimsSub: Subscription;
claimRebuttalsSub: Subscription;
searchTermsSub: Subscription;
// Things that need debouncing
searchTerms$ = new Subject<string>();
scrollY$ = new Subject<string>();
// Component State
options: SortablejsOptions = { disabled: true };
page: BerniePageLayout;
shallowClaims: { [index: string]: Claim };
claims: Claim[];
claimRebuttals: Readonly<ClaimRebuttal[]>;
selectedClaimId: string;
searchTerms: string;
constructor(private store: Store<fromRoot.RootState>,
private router: Router,
private route: ActivatedRoute) {
}
ngOnInit() {
this.pageSub = this.store.select(fromRoot.getBerniePageState).subscribe((page) => {
this.page = page;
});
this.claimsSub = this.store.select(fromRoot.getDeepClaims).subscribe((claims) => {
this.selectedClaimId = claims.selectedClaimId;
this.claims = claims.deepClaims;
this.shallowClaims = claims.shallowClaims; // we need this to do claims reordering
if (this.selectedClaimId !== null && this.shallowClaims[this.selectedClaimId] && !this.shallowClaims[this.selectedClaimId].expanded) {
// by this point a Select action has already been dispatched and the selectedEntityId set
this.store.dispatch(new EntityActions.Patch(slices.CLAIM, { id: this.selectedClaimId, expanded: true }));
this.scrollY$.next(this.selectedClaimId);
}
})
this.claimRebuttalsSub = this.store.select(fromRoot.getClaimRebuttals).subscribe((claimRebuttals) => {
this.claimRebuttals = claimRebuttals;
});
this.searchTermsSub = this.store.select(fromRoot.getBernieSearchTerm).subscribe((terms) => {
this.searchTerms = terms;
});
this.searchTerms$
.debounceTime(400) // wait 400ms after each keystroke before considering the term
.distinctUntilChanged() // ignore if next search term is same as previous
.subscribe((term) => {
this.navigate(term)
});
// This is so that the scrolling happens after the data is fetched and the dom is loaded
this.scrollY$
.debounceTime(20) // this must be an amount of time greater than what it takes to load the dom
.distinctUntilChanged()
.subscribe((claimId) => {
const claimBox = window.document.getElementById('claim-box');
const y = window.document.getElementById(claimId).offsetTop;
claimBox.scrollTo(0, y - 5); // scroll to the selected Claim minus a smidge of space
})
this.store.dispatch(new EntityActions.Unload(slices.CLAIM));
this.store.dispatch(new EntityActions.Unload(slices.CLAIM_REBUTTAL));
this.store.dispatch(new EntityActions.Unload(slices.REBUTTAL));
this.store.dispatch(new EntityActions.Load(slices.CLAIM));
this.store.dispatch(new EntityActions.Load(slices.CLAIM_REBUTTAL));
this.store.dispatch(new EntityActions.Load(slices.REBUTTAL));
}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms$.next(term);
}
navigate(term: string = this.searchTerms, claimId: string = this.selectedClaimId) {
const url = '/features/bernie'
+ (claimId !== null ? `/${claimId}` : '')
+ (term ? `?q=${term}` : '');
this.router.navigateByUrl(url);
}
toggleEditable() {
this.store.dispatch(new SliceActions.Update(slices.LAYOUT, ['berniePage', 'editable'], !this.page.editable));
this.options = { disabled: !this.options.disabled };
}
toggleExpanded() {
const expanded = this.page.expanded;
this.store.dispatch(new SliceActions.Update(slices.LAYOUT, ['berniePage', 'expanded'], !expanded));
this.store.dispatch(new EntityActions.PatchEach(slices.CLAIM, { expanded: !expanded }));
}
addClaim() {
const newClaimName = prompt('New claim');
if (newClaimName) {
this.store.dispatch(new EntityActions.AddTemp(slices.CLAIM, {
name: newClaimName
}));
}
}
saveAll() {
alert('Add save here');
}
addRebuttal(claim: Claim) {
// claims & rebuttals have a many-to-many relationship
// to create a new, blank rebuttal for this claim
// create an instance of rebuttal and an instance of the join record claimRebuttal
// and dispatch actions to each respective reducer
this.store.dispatch(new EntityActions.AddTemp(slices.REBUTTAL, { editing: true }));
this.store.dispatch(new EntityActions.AddTemp(slices.CLAIM_REBUTTAL, { claimId: claim.id, rebuttalId: EntityActions.TEMP }));
}
toggleRebuttals(claim: { id: string, expanded: boolean }) {
if (this.selectedClaimId === claim.id && claim.expanded) {
this.store.dispatch(new EntityActions.Select(slices.CLAIM, { id: null }));
}
this.store.dispatch(new EntityActions.Patch<ClaimFields>(slices.CLAIM, { id: claim.id, expanded: !claim.expanded }));
}
cancelRebuttal({ claimRebuttalId, rebuttal }) {
if (rebuttal.id === EntityActions.TEMP) {
this.store.dispatch(new EntityActions.DeleteTemp(slices.CLAIM_REBUTTAL));
this.store.dispatch(new EntityActions.DeleteTemp(slices.REBUTTAL));
} else {
this.store.dispatch(new EntityActions.Patch<RebuttalFields>(slices.REBUTTAL, { id: rebuttal.id, editing: false }));
}
}
saveRebuttal(newRebuttal) {
this.store.dispatch(new EntityActions.Update<Rebuttal>(slices.REBUTTAL, Object.assign({}, newRebuttal, { editing: false })));
}
makeRebuttalEditable(rebuttal: Rebuttal) {
this.store.dispatch(new EntityActions.Patch<RebuttalFields>(slices.REBUTTAL, { id: rebuttal.id, editing: true }));
}
reorderRebuttals(claim, event) {
const rebuttalIds = Array.prototype.slice.call(event.srcElement.children).filter((li) => li.id).map((li) => li.id);
// Otherwise sortablejs gets a null event and throws an error
setTimeout(() => {
let i = 1;
rebuttalIds.map((rebuttalId) => {
const crid = this.claimRebuttals.filter((cr) => cr.claimId === claim.id && cr.rebuttalId === rebuttalId)[0].id;
this.store.dispatch(new EntityActions.Patch(slices.CLAIM_REBUTTAL, { id: crid, sortOrder: i }));
i++;
})
}, 0);
}
reorderClaims(event) {
if (event.srcElement.children[0].children[0].localName === 'jhi-bernie-rebuttal') { // TODO: find a better way
return;
}
setTimeout(() => { // need this setTimeout or else sortablejs event handling gets screwed up
// get the claim ids in the updated order from the DOM LIs IS THERE A BETTER WAY TO GET THESE?
const ids = Array.prototype.slice.call(event.srcElement.children).map((li) => li.children[0].children[0].children[0].id);
// get an updated hash of entities by updating sortOrder of the old ones
const entities = Object.assign({}, this.shallowClaims);
ids.map((id, index) => {
entities[id].sortOrder = index;
})
this.store.dispatch(new SliceActions.Patch(slices.CLAIM, [], { entities }));
})
}
ngOnDestroy() {
this.pageSub && this.pageSub.unsubscribe();
this.claimsSub && this.claimsSub.unsubscribe();
this.claimRebuttalsSub && this.claimRebuttalsSub.unsubscribe();
this.searchTermsSub && this.searchTermsSub.unsubscribe();
}
}