@nyaruka/temba-components 0.33.3 → 0.33.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.github/workflows/build.yml +4 -5
  2. package/CHANGELOG.md +33 -3
  3. package/demo/index.html +1 -1
  4. package/dist/{7b0d8aca.js → 72be961a.js} +304 -78
  5. package/dist/index.js +304 -78
  6. package/dist/sw.js +1 -1
  7. package/dist/sw.js.map +1 -1
  8. package/dist/templates/components-body.html +1 -1
  9. package/dist/templates/components-head.html +1 -1
  10. package/out-tsc/src/FormElement.js +9 -6
  11. package/out-tsc/src/FormElement.js.map +1 -1
  12. package/out-tsc/src/contacts/ContactDetails.js +1 -1
  13. package/out-tsc/src/contacts/ContactDetails.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactFields.js +6 -5
  15. package/out-tsc/src/contacts/ContactFields.js.map +1 -1
  16. package/out-tsc/src/contacts/ContactHistory.js +4 -4
  17. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  18. package/out-tsc/src/fields/FieldManager.js +323 -0
  19. package/out-tsc/src/fields/FieldManager.js.map +1 -0
  20. package/out-tsc/src/interfaces.js +3 -0
  21. package/out-tsc/src/interfaces.js.map +1 -1
  22. package/out-tsc/src/list/SortableList.js +193 -0
  23. package/out-tsc/src/list/SortableList.js.map +1 -0
  24. package/out-tsc/src/store/Store.js +28 -17
  25. package/out-tsc/src/store/Store.js.map +1 -1
  26. package/out-tsc/src/store/StoreElement.js +1 -0
  27. package/out-tsc/src/store/StoreElement.js.map +1 -1
  28. package/out-tsc/src/vectoricon/index.js +7 -3
  29. package/out-tsc/src/vectoricon/index.js.map +1 -1
  30. package/out-tsc/temba-modules.js +4 -0
  31. package/out-tsc/temba-modules.js.map +1 -1
  32. package/out-tsc/test/temba-field-manager.test.js +47 -0
  33. package/out-tsc/test/temba-field-manager.test.js.map +1 -0
  34. package/out-tsc/test/temba-sortable-list.test.js +48 -0
  35. package/out-tsc/test/temba-sortable-list.test.js.map +1 -0
  36. package/out-tsc/test/temba-store.test.js +1 -1
  37. package/out-tsc/test/temba-store.test.js.map +1 -1
  38. package/package.json +1 -1
  39. package/screenshots/truth/list/fields-dragging.png +0 -0
  40. package/screenshots/truth/list/fields-filtered.png +0 -0
  41. package/screenshots/truth/list/fields-hovered.png +0 -0
  42. package/screenshots/truth/list/fields.png +0 -0
  43. package/screenshots/truth/list/sortable-dragging.png +0 -0
  44. package/screenshots/truth/list/sortable-dropped.png +0 -0
  45. package/screenshots/truth/list/sortable.png +0 -0
  46. package/src/FormElement.ts +9 -6
  47. package/src/contacts/ContactDetails.ts +1 -1
  48. package/src/contacts/ContactFields.ts +6 -5
  49. package/src/contacts/ContactHistory.ts +4 -4
  50. package/src/fields/FieldManager.ts +353 -0
  51. package/src/interfaces.ts +5 -1
  52. package/src/list/SortableList.ts +224 -0
  53. package/src/store/Store.ts +34 -21
  54. package/src/store/StoreElement.ts +1 -0
  55. package/src/vectoricon/index.ts +7 -3
  56. package/static/svg/index.pdf +274 -0
  57. package/temba-modules.ts +4 -0
  58. package/test/temba-field-manager.test.ts +60 -0
  59. package/test/temba-sortable-list.test.ts +58 -0
  60. package/test/temba-store.test.ts +1 -1
  61. package/test-assets/store/fields.json +50 -5
@@ -0,0 +1,353 @@
1
+ import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
+ import { property } from 'lit/decorators';
3
+ import { ContactField, CustomEventType } from '../interfaces';
4
+
5
+ import { SortableList } from '../list/SortableList';
6
+ import { StoreElement } from '../store/StoreElement';
7
+ import { postJSON } from '../utils';
8
+
9
+ const TYPE_NAMES = {
10
+ text: 'Text',
11
+ numeric: 'Number',
12
+ number: 'Number',
13
+ datetime: 'Date & Time',
14
+ state: 'State',
15
+ ward: 'Ward',
16
+ district: 'District',
17
+ };
18
+
19
+ const matches = (field: ContactField, query: string): boolean => {
20
+ if (!query) {
21
+ return true;
22
+ }
23
+ const search = (
24
+ field.label +
25
+ field.key +
26
+ TYPE_NAMES[field.value_type]
27
+ ).toLowerCase();
28
+ if (search.toLowerCase().indexOf(query) > -1) {
29
+ return true;
30
+ }
31
+ return false;
32
+ };
33
+
34
+ export class FieldManager extends StoreElement {
35
+ static get styles() {
36
+ return css`
37
+ :host {
38
+ display: flex;
39
+ flex-grow: 1;
40
+ flex-direction: column;
41
+ min-height: 0px;
42
+ }
43
+
44
+ .featured,
45
+ .other-fields {
46
+ background: #fff;
47
+ border-radius: var(--curvature);
48
+ box-shadow: var(--shadow);
49
+ margin-bottom: 1em;
50
+ display: flex;
51
+ flex-direction: column;
52
+ }
53
+
54
+ .featured {
55
+ max-height: 40%;
56
+ }
57
+
58
+ .other-fields {
59
+ flex-grow: 2;
60
+ min-height: 0px;
61
+ }
62
+
63
+ temba-textinput {
64
+ margin-bottom: 1em;
65
+ }
66
+
67
+ .scroll-box {
68
+ overflow-y: auto;
69
+ flex-grow: 1;
70
+ flex-direction: column;
71
+ display: flex;
72
+ }
73
+
74
+ .header temba-icon {
75
+ margin-right: 0.5em;
76
+ }
77
+
78
+ .label {
79
+ flex-grow: 1;
80
+ }
81
+
82
+ .header {
83
+ padding: 0.5em 1em;
84
+ display: flex;
85
+ align-items: flex-start;
86
+ border-bottom: 1px solid var(--color-widget-border);
87
+ }
88
+
89
+ .featured-field {
90
+ user-select: none;
91
+ }
92
+
93
+ temba-sortable-list {
94
+ padding: 0.5em 0em;
95
+ width: 100%;
96
+ overflow-y: auto;
97
+ }
98
+
99
+ .scroll-box {
100
+ padding: 0.5em 0em;
101
+ }
102
+
103
+ temba-icon[name='usages']:hover {
104
+ --icon-color: var(--color-link-primary);
105
+ }
106
+
107
+ .field:hover temba-icon[name='delete_small'] {
108
+ opacity: 1 !important;
109
+ cursor: pointer !important;
110
+ pointer-events: all !important;
111
+ }
112
+
113
+ temba-icon[name='delete_small']:hover {
114
+ --icon-color: var(--color-link-primary);
115
+ }
116
+
117
+ .field {
118
+ border: 1px solid transparent;
119
+ margin: 0 0.5em;
120
+ border-radius: var(--curvature);
121
+ }
122
+
123
+ .featured temba-sortable-list .field:hover {
124
+ cursor: move;
125
+ border-color: #e6e6e6;
126
+ background: #fcfcfc;
127
+ }
128
+ `;
129
+ }
130
+
131
+ @property({ type: String, attribute: 'priority-endpoint' })
132
+ priorityEndpoint: string;
133
+
134
+ @property({ type: Object, attribute: false })
135
+ featuredFields: ContactField[];
136
+
137
+ @property({ type: Object, attribute: false })
138
+ otherFieldKeys: string[] = [];
139
+
140
+ @property({ type: String })
141
+ draggingId: string;
142
+
143
+ @property({ type: String })
144
+ query = '';
145
+
146
+ protected firstUpdated(
147
+ _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
148
+ ): void {
149
+ super.firstUpdated(_changedProperties);
150
+ this.url = this.store.fieldsEndpoint;
151
+ }
152
+
153
+ private filterFields() {
154
+ const filteredKeys = this.store.getFieldKeys().filter(key => {
155
+ const field = this.store.getContactField(key);
156
+ if (field.featured) {
157
+ return false;
158
+ }
159
+ return matches(field, this.query);
160
+ });
161
+
162
+ // sort by the label instead of the key
163
+ filteredKeys.sort((a, b) => {
164
+ return this.store
165
+ .getContactField(a)
166
+ .label.localeCompare(this.store.getContactField(b).label);
167
+ });
168
+
169
+ const featured: ContactField[] = [];
170
+ this.store.getFeaturedFields().forEach(field => {
171
+ if (matches(field, this.query)) {
172
+ featured.push(field);
173
+ }
174
+ });
175
+
176
+ this.otherFieldKeys = filteredKeys;
177
+ this.featuredFields = featured;
178
+ }
179
+
180
+ protected updated(
181
+ properties: PropertyValueMap<any> | Map<PropertyKey, unknown>
182
+ ): void {
183
+ super.update(properties);
184
+ if (properties.has('data')) {
185
+ this.filterFields();
186
+ } else if (properties.has('query')) {
187
+ this.filterFields();
188
+ }
189
+ }
190
+
191
+ private handleSaveOrder(event) {
192
+ const list = event.currentTarget as SortableList;
193
+ postJSON(
194
+ this.priorityEndpoint,
195
+ list
196
+ .getIds()
197
+ .reverse()
198
+ .reduce((map, key, idx) => {
199
+ map[key] = idx;
200
+ return map;
201
+ }, {})
202
+ ).then(() => {
203
+ this.store.refreshFields();
204
+ });
205
+ }
206
+
207
+ private handleOrderChanged(event) {
208
+ const swapsies = event.detail;
209
+ const temp = this.featuredFields[swapsies.fromIdx];
210
+ this.featuredFields[swapsies.fromIdx] = this.featuredFields[swapsies.toIdx];
211
+ this.featuredFields[swapsies.toIdx] = temp;
212
+ this.requestUpdate('featuredFields');
213
+ }
214
+
215
+ private handleDragStart(event) {
216
+ this.draggingId = event.detail.id;
217
+ }
218
+
219
+ private handleDragStop() {
220
+ this.draggingId = null;
221
+ }
222
+
223
+ private handleFieldAction(event: MouseEvent) {
224
+ const ele = event.target as HTMLDivElement;
225
+ const key = ele.dataset.key;
226
+ const action = ele.dataset.action;
227
+ this.fireCustomEvent(CustomEventType.Selection, { key, action });
228
+ }
229
+
230
+ private handleSearch(event) {
231
+ this.query = (event.target.value || '').trim();
232
+ }
233
+
234
+ private hasUsages(field: ContactField): boolean {
235
+ return (
236
+ field.usages.campaign_events + field.usages.flows + field.usages.groups >
237
+ 0
238
+ );
239
+ }
240
+
241
+ private renderField(field: ContactField) {
242
+ return html`
243
+ <div
244
+ class="field sortable"
245
+ id="${field.key}"
246
+ style="
247
+ display: flex;
248
+ flex-direction: row;
249
+ align-items: center;
250
+ padding: 0.25em 1em;
251
+ ${field.key === this.draggingId
252
+ ? 'background: var(--color-selection)'
253
+ : ''}"
254
+ >
255
+ <div
256
+ style="display: flex; min-width: 200px; width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 2em"
257
+ >
258
+ <span
259
+ @click=${this.handleFieldAction}
260
+ data-key=${field.key}
261
+ data-action="update"
262
+ style="color: var(--color-link-primary); cursor:pointer;"
263
+ >
264
+ ${field.label}
265
+ </span>
266
+ ${this.hasUsages(field)
267
+ ? html`
268
+ <temba-icon
269
+ size="0.8"
270
+ style="color: #ccc; margin-left: 0.7em;"
271
+ name="usages"
272
+ data-key=${field.key}
273
+ data-action="usages"
274
+ @click=${this.handleFieldAction}
275
+ clickable
276
+ ></temba-icon>
277
+ `
278
+ : null}
279
+ <div class="flex-grow:1"></div>
280
+ </div>
281
+ <div style="flex-grow:1; font-family: monospace; font-size:0.8em;">
282
+ @fields.${field.key}
283
+ </div>
284
+ <div>${TYPE_NAMES[field.value_type]}</div>
285
+ <temba-icon
286
+ style="pointer-events:none;color:#ccc;margin-left:0.3em;margin-right:-0.5em;opacity:0"
287
+ name="delete_small"
288
+ data-key=${field.key}
289
+ data-action="delete"
290
+ @click=${this.handleFieldAction}
291
+ ></temba-icon>
292
+ </div>
293
+ `;
294
+ }
295
+
296
+ public render(): TemplateResult {
297
+ if (!this.featuredFields) {
298
+ return null;
299
+ }
300
+
301
+ return html`
302
+ <temba-textinput
303
+ id="search"
304
+ placeholder="Search"
305
+ @change=${this.handleSearch}
306
+ clearable
307
+ ></temba-textinput>
308
+
309
+ ${this.featuredFields.length > 0
310
+ ? html`
311
+ <div class="featured">
312
+ <div class="header">
313
+ <temba-icon name="featured"></temba-icon>
314
+ <div class="label">Featured</div>
315
+ </div>
316
+ ${this.query
317
+ ? html`
318
+ <div class="scroll-box">
319
+ ${this.featuredFields.map(field =>
320
+ this.renderField(field)
321
+ )}
322
+ </div>
323
+ `
324
+ : html`
325
+ <temba-sortable-list
326
+ @change=${this.handleSaveOrder}
327
+ @temba-order-changed=${this.handleOrderChanged}
328
+ @temba-drag-start=${this.handleDragStart}
329
+ @temba-drag-stop=${this.handleDragStop}
330
+ >
331
+ ${this.featuredFields.map(field =>
332
+ this.renderField(field)
333
+ )}
334
+ </temba-sortable-list>
335
+ `}
336
+ </div>
337
+ `
338
+ : null}
339
+
340
+ <div class="other-fields">
341
+ <div class="header">
342
+ <temba-icon name="fields"></temba-icon>
343
+ <div class="label">Everything Else</div>
344
+ </div>
345
+ <div class="scroll-box">
346
+ ${this.otherFieldKeys.map(field =>
347
+ this.renderField(this.store.getContactField(field))
348
+ )}
349
+ </div>
350
+ </div>
351
+ `;
352
+ }
353
+ }
package/src/interfaces.ts CHANGED
@@ -97,8 +97,9 @@ export interface ContactField {
97
97
  key: string;
98
98
  label: string;
99
99
  value_type: string;
100
- pinned: boolean;
100
+ featured: boolean;
101
101
  priority: number;
102
+ usages: { campaign_events: number; flows: number; groups: number };
102
103
  }
103
104
 
104
105
  export interface ContactGroup {
@@ -239,4 +240,7 @@ export enum CustomEventType {
239
240
  NoPath = 'temba-no-path',
240
241
  StoreUpdated = 'temba-store-updated',
241
242
  Ready = 'temba-ready',
243
+ OrderChanged = 'temba-order-changed',
244
+ DragStart = 'temba-drag-start',
245
+ DragStop = 'temba-drag-stop',
242
246
  }
@@ -0,0 +1,224 @@
1
+ import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
+ import { property } from 'lit/decorators';
3
+ import { CustomEventType } from '../interfaces';
4
+ import { RapidElement } from '../RapidElement';
5
+
6
+ /**
7
+ * A simple list that can be sorted by dragging
8
+ */
9
+
10
+ // how far we have to drag before it starts
11
+ const DRAG_THRESHOLD = 5;
12
+ export class SortableList extends RapidElement {
13
+ static get styles() {
14
+ return css`
15
+ :host {
16
+ margin: auto;
17
+ }
18
+
19
+ .container {
20
+ user-select: none;
21
+ }
22
+
23
+ .dragging {
24
+ background: var(--color-selection);
25
+ }
26
+
27
+ .sortable {
28
+ transition: all 300ms ease-in-out;
29
+ display: flex;
30
+ padding: 0.4em 0;
31
+ }
32
+
33
+ .sortable:hover temba-icon {
34
+ opacity: 1;
35
+ cursor: move;
36
+ }
37
+
38
+ .ghost {
39
+ position: absolute;
40
+ opacity: 0.5;
41
+ transition: none;
42
+ }
43
+
44
+ .slot {
45
+ flex-grow: 1;
46
+ }
47
+
48
+ slot > * {
49
+ user-select: none;
50
+ }
51
+
52
+ temba-icon {
53
+ opacity: 0.1;
54
+ padding: 0.2em 0.5em;
55
+ transition: all 300ms ease-in-out;
56
+ }
57
+ `;
58
+ }
59
+
60
+ @property({ type: String })
61
+ draggingId: string;
62
+
63
+ ghostElement: HTMLDivElement = null;
64
+ downEle: HTMLDivElement = null;
65
+ xOffset = 0;
66
+ yOffset = 0;
67
+ yDown = 0;
68
+
69
+ draggingIdx = -1;
70
+ draggingEle = null;
71
+
72
+ public constructor() {
73
+ super();
74
+ this.handleMouseMove = this.handleMouseMove.bind(this);
75
+ this.handleMouseUp = this.handleMouseUp.bind(this);
76
+ this.handleMouseDown = this.handleMouseDown.bind(this);
77
+ }
78
+
79
+ protected firstUpdated(
80
+ _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
81
+ ): void {
82
+ super.firstUpdated(_changedProperties);
83
+ }
84
+
85
+ public getIds() {
86
+ return this.shadowRoot
87
+ .querySelector('slot')
88
+ .assignedElements()
89
+ .map(ele => ele.id);
90
+ }
91
+
92
+ private getRowIndex(id: string): number {
93
+ return this.shadowRoot
94
+ .querySelector('slot')
95
+ .assignedElements()
96
+ .findIndex(ele => ele.id === id);
97
+ }
98
+
99
+ private getOverlappingElement(mouseY: number): HTMLDivElement {
100
+ const ghostRect = this.ghostElement.getBoundingClientRect();
101
+
102
+ const ele = this.shadowRoot
103
+ .querySelector('slot')
104
+ .assignedElements()
105
+ .find(otherEle => {
106
+ const rect = otherEle.getBoundingClientRect();
107
+
108
+ // don't return ourselves
109
+ if (otherEle.id === this.ghostElement.id) {
110
+ return false;
111
+ }
112
+
113
+ if (mouseY > this.yDown) {
114
+ // moving down
115
+ return ghostRect.top < rect.bottom && ghostRect.bottom > rect.bottom;
116
+ } else {
117
+ // moving up
118
+ return rect.top < ghostRect.bottom && rect.bottom > ghostRect.bottom;
119
+ }
120
+ });
121
+ return ele as HTMLDivElement;
122
+ }
123
+
124
+ private handleMouseDown(event: MouseEvent) {
125
+ let ele = event.target as HTMLDivElement;
126
+ ele = ele.closest('.sortable');
127
+ if (ele) {
128
+ this.downEle = ele;
129
+ this.draggingId = ele.id;
130
+ this.draggingIdx = this.getRowIndex(ele.id);
131
+ this.draggingEle = ele;
132
+
133
+ this.xOffset = event.clientX - ele.offsetLeft;
134
+ this.yOffset = event.clientY - ele.offsetTop;
135
+ this.yDown = event.clientY;
136
+
137
+ document.addEventListener('mousemove', this.handleMouseMove);
138
+ document.addEventListener('mouseup', this.handleMouseUp);
139
+ }
140
+ }
141
+
142
+ private handleMouseMove(event: MouseEvent) {
143
+ const scrollTop = this.shadowRoot
144
+ .querySelector('slot')
145
+ .assignedElements()[0].parentElement.scrollTop;
146
+
147
+ if (
148
+ !this.ghostElement &&
149
+ this.downEle &&
150
+ Math.abs(event.clientY - this.yDown) > DRAG_THRESHOLD
151
+ ) {
152
+ this.fireCustomEvent(CustomEventType.DragStart, {
153
+ id: this.downEle.id,
154
+ });
155
+
156
+ this.ghostElement = this.downEle.cloneNode(true) as HTMLDivElement;
157
+ this.ghostElement.classList.add('ghost');
158
+
159
+ const computedStyle = getComputedStyle(this.downEle);
160
+
161
+ this.ghostElement.style.width =
162
+ this.downEle.clientWidth -
163
+ parseFloat(computedStyle.paddingLeft) -
164
+ parseFloat(computedStyle.paddingRight) +
165
+ 'px';
166
+ const container = this.shadowRoot.querySelector('.container');
167
+
168
+ container.appendChild(this.ghostElement);
169
+
170
+ this.downEle = null;
171
+ }
172
+
173
+ if (this.ghostElement) {
174
+ this.ghostElement.style.left = event.clientX - this.xOffset + 'px';
175
+ this.ghostElement.style.top =
176
+ event.clientY - this.yOffset - scrollTop + 'px';
177
+
178
+ const other = this.getOverlappingElement(event.clientY);
179
+ if (other) {
180
+ const otherIdx = this.getRowIndex(other.id);
181
+ const dragId = this.ghostElement.id;
182
+ const otherId = other.id;
183
+
184
+ this.fireCustomEvent(CustomEventType.OrderChanged, {
185
+ from: dragId,
186
+ to: otherId,
187
+ fromIdx: this.draggingIdx,
188
+ toIdx: otherIdx,
189
+ });
190
+
191
+ // TODO: Dont do swapping, just send the full order?
192
+ this.draggingIdx = otherIdx;
193
+ this.draggingId = otherId;
194
+ }
195
+ }
196
+ }
197
+
198
+ private handleMouseUp() {
199
+ if (this.draggingId) {
200
+ this.fireCustomEvent(CustomEventType.DragStop, {
201
+ id: this.draggingId,
202
+ });
203
+
204
+ this.draggingId = null;
205
+ this.downEle = null;
206
+
207
+ if (this.ghostElement) {
208
+ this.ghostElement.remove();
209
+ this.ghostElement = null;
210
+ }
211
+ }
212
+ document.removeEventListener('mousemove', this.handleMouseMove);
213
+ document.removeEventListener('mouseup', this.handleMouseUp);
214
+ this.dispatchEvent(new Event('change'));
215
+ }
216
+
217
+ public render(): TemplateResult {
218
+ return html`
219
+ <div class="container">
220
+ <slot @mousedown=${this.handleMouseDown}></slot>
221
+ </div>
222
+ `;
223
+ }
224
+ }
@@ -64,7 +64,7 @@ export class Store extends RapidElement {
64
64
  private groups: { [uuid: string]: ContactGroup } = {};
65
65
  private languages: any = {};
66
66
  private workspace: Workspace;
67
- private pinnedFields: ContactField[] = [];
67
+ private featuredFields: ContactField[] = [];
68
68
 
69
69
  private langService = new HumanizeDurationLanguage();
70
70
  private humanizer = new HumanizeDuration(this.langService);
@@ -103,24 +103,7 @@ export class Store extends RapidElement {
103
103
  }
104
104
 
105
105
  if (this.fieldsEndpoint) {
106
- fetches.push(
107
- getAssets(this.fieldsEndpoint).then((assets: Asset[]) => {
108
- this.keyedAssets['fields'] = [];
109
- this.pinnedFields = [];
110
-
111
- assets.forEach((field: ContactField) => {
112
- this.keyedAssets['fields'].push(field.key);
113
- this.fields[field.key] = field;
114
- if (field.pinned) {
115
- this.pinnedFields.push(field);
116
- }
117
- });
118
-
119
- this.pinnedFields.sort((a, b) => {
120
- return b.priority - a.priority;
121
- });
122
- })
123
- );
106
+ fetches.push(this.refreshFields());
124
107
  }
125
108
 
126
109
  if (this.globalsEndpoint) {
@@ -183,6 +166,32 @@ export class Store extends RapidElement {
183
166
  return 'en';
184
167
  }
185
168
 
169
+ public refreshFields() {
170
+ return getAssets(this.fieldsEndpoint).then((assets: Asset[]) => {
171
+ this.keyedAssets['fields'] = [];
172
+ this.featuredFields = [];
173
+
174
+ assets.forEach((field: ContactField) => {
175
+ this.keyedAssets['fields'].push(field.key);
176
+ this.fields[field.key] = field;
177
+ if (field.featured) {
178
+ this.featuredFields.push(field);
179
+ }
180
+ });
181
+
182
+ this.featuredFields.sort((a, b) => {
183
+ return b.priority - a.priority;
184
+ });
185
+
186
+ this.keyedAssets['fields'].sort();
187
+
188
+ this.fireCustomEvent(CustomEventType.StoreUpdated, {
189
+ url: this.fieldsEndpoint,
190
+ data: this.keyedAssets['fields'],
191
+ });
192
+ });
193
+ }
194
+
186
195
  public getShortDuration(
187
196
  isoDateA: string,
188
197
  isoDateB: string = null,
@@ -232,12 +241,16 @@ export class Store extends RapidElement {
232
241
  return this.keyedAssets;
233
242
  }
234
243
 
244
+ public getFieldKeys(): string[] {
245
+ return this.keyedAssets['fields'];
246
+ }
247
+
235
248
  public getContactField(key: string): ContactField {
236
249
  return this.fields[key];
237
250
  }
238
251
 
239
- public getPinnedFields(): ContactField[] {
240
- return this.pinnedFields;
252
+ public getFeaturedFields(): ContactField[] {
253
+ return this.featuredFields;
241
254
  }
242
255
 
243
256
  public getLanguageName(iso: string) {
@@ -32,6 +32,7 @@ export class StoreElement extends RapidElement {
32
32
  if (event.detail.url === this.url) {
33
33
  this.data = event.detail.data;
34
34
  this.fireCustomEvent(CustomEventType.Refreshed, { data: this.data });
35
+ // console.log("Updated!", this.data);
35
36
  }
36
37
  }
37
38