@nyaruka/temba-components 0.67.3 → 0.68.0

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 (68) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/demo/index.html +5 -3
  3. package/dist/{314b925e.js → dd629ee4.js} +802 -122
  4. package/dist/index.js +802 -122
  5. package/dist/static/svg/index.svg +1 -1
  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/contacts/ContactTickets.js +4 -6
  11. package/out-tsc/src/contacts/ContactTickets.js.map +1 -1
  12. package/out-tsc/src/contacts/events.js +3 -8
  13. package/out-tsc/src/contacts/events.js.map +1 -1
  14. package/out-tsc/src/imagepicker/CroppieCSS.js +254 -0
  15. package/out-tsc/src/imagepicker/CroppieCSS.js.map +1 -0
  16. package/out-tsc/src/imagepicker/ImagePicker.js +248 -0
  17. package/out-tsc/src/imagepicker/ImagePicker.js.map +1 -0
  18. package/out-tsc/src/interfaces.js.map +1 -1
  19. package/out-tsc/src/list/NotificationList.js +4 -0
  20. package/out-tsc/src/list/NotificationList.js.map +1 -1
  21. package/out-tsc/src/list/TembaMenu.js +1 -1
  22. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  23. package/out-tsc/src/list/TicketList.js +16 -12
  24. package/out-tsc/src/list/TicketList.js.map +1 -1
  25. package/out-tsc/src/mask/Mask.js +36 -0
  26. package/out-tsc/src/mask/Mask.js.map +1 -0
  27. package/out-tsc/src/utils/index.js +24 -16
  28. package/out-tsc/src/utils/index.js.map +1 -1
  29. package/out-tsc/src/vectoricon/index.js +5 -1
  30. package/out-tsc/src/vectoricon/index.js.map +1 -1
  31. package/out-tsc/src/webchat/WebChat.js +515 -0
  32. package/out-tsc/src/webchat/WebChat.js.map +1 -0
  33. package/out-tsc/src/webchat/index.js +7 -0
  34. package/out-tsc/src/webchat/index.js.map +1 -0
  35. package/out-tsc/temba-modules.js +6 -0
  36. package/out-tsc/temba-modules.js.map +1 -1
  37. package/package.json +4 -2
  38. package/screenshots/truth/contacts/tickets-assignment.png +0 -0
  39. package/screenshots/truth/contacts/tickets.png +0 -0
  40. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  41. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  42. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  43. package/screenshots/truth/menu/menu-root.png +0 -0
  44. package/screenshots/truth/menu/menu-submenu.png +0 -0
  45. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  46. package/screenshots/truth/menu/menu-tasks.png +0 -0
  47. package/src/contacts/ContactTickets.ts +4 -6
  48. package/src/contacts/events.ts +3 -9
  49. package/src/imagepicker/CroppieCSS.ts +254 -0
  50. package/src/imagepicker/ImagePicker.ts +272 -0
  51. package/src/interfaces.ts +2 -1
  52. package/src/list/NotificationList.ts +3 -0
  53. package/src/list/TembaMenu.ts +1 -1
  54. package/src/list/TicketList.ts +16 -12
  55. package/src/mask/Mask.ts +32 -0
  56. package/src/untyped.d.ts +1 -0
  57. package/src/utils/index.ts +26 -18
  58. package/src/vectoricon/index.ts +5 -1
  59. package/src/webchat/WebChat.ts +559 -0
  60. package/src/webchat/index.ts +6 -0
  61. package/static/svg/index.svg +1 -1
  62. package/static/svg/webchat.svg +1 -0
  63. package/static/svg/work/traced/camera-01.svg +1 -0
  64. package/static/svg/work/traced/send-03.svg +1 -0
  65. package/static/svg/work/used/camera-01.svg +4 -0
  66. package/static/svg/work/used/send-03.svg +3 -0
  67. package/svg.js +12 -8
  68. package/temba-modules.ts +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.67.3",
3
+ "version": "0.68.0",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,8 @@
26
26
  "test:watch": "wtr --node-resolve --watch",
27
27
  "storybook": "concurrently --kill-others --names tsc,storybook \"npm run tsc:watch\" \"start-storybook --node-resolve --watch --open\"",
28
28
  "storybook:build": "build-storybook",
29
- "svg": "rimraf static/svg/work && node svg.js --resolution=150",
29
+ "svg": "rimraf static/svg/work && node svg.js --resolution=150 --output='./static/svg/index.svg' --usage='./src/vectoricon/index.ts'",
30
+ "svg-wc": "rimraf static/svg/work && node svg.js --resolution=150 --output='./static/svg/webchat.svg' --usage='./src/webchat/index.ts'",
30
31
  "version": "yarn run build && auto-changelog -p && git add CHANGELOG.md",
31
32
  "locale:extract": "lit-localize extract --config localize.config.json",
32
33
  "locale:build": "lit-localize build --config localize.config.json"
@@ -34,6 +35,7 @@
34
35
  "dependencies": {
35
36
  "@lit/localize": "^0.11.4",
36
37
  "color-hash": "^2.0.2",
38
+ "croppie": "^2.6.5",
37
39
  "geojson": "^0.5.0",
38
40
  "highlight.js": "^10.7.1",
39
41
  "image-size": "^0.9.7",
@@ -155,7 +155,6 @@ export class ContactTickets extends StoreElement {
155
155
 
156
156
  .user {
157
157
  display: flex;
158
- padding: 0.4em 0.7em;
159
158
  align-items: center;
160
159
  border-radius: var(--curvature);
161
160
  cursor: pointer;
@@ -166,8 +165,6 @@ export class ContactTickets extends StoreElement {
166
165
  }
167
166
 
168
167
  .user .avatar {
169
- font-size: 0.5em;
170
- margin-right: 1em;
171
168
  }
172
169
 
173
170
  .user .name {
@@ -251,7 +248,7 @@ export class ContactTickets extends StoreElement {
251
248
  return null;
252
249
  }
253
250
  return html`<div class="user">
254
- <div class="avatar">${renderAvatar({ user: user })}</div>
251
+ <div class="avatar">${renderAvatar({ user: user, scale: 0.6 })}</div>
255
252
  <div class="name">${getFullName(user)}</div>
256
253
  </div>`;
257
254
  }
@@ -355,10 +352,11 @@ export class ContactTickets extends StoreElement {
355
352
  <div slot="toggle" class="toggle">
356
353
  ${ticket.assignee
357
354
  ? html`
358
- <div style="font-size:0.5em">
355
+ <div>
359
356
  ${renderAvatar({
360
357
  name: ticket.assignee.name,
361
- position: 'left',
358
+ user: ticket.assignee,
359
+ scale: 0.7,
362
360
  })}
363
361
  </div>
364
362
  `
@@ -697,12 +697,6 @@ export const getEventGroupType = (event: ContactEvent, ticket: string) => {
697
697
  return 'verbose';
698
698
  };
699
699
 
700
- export const renderUserAvatar = (user: User) => {
701
- return html`<div style="width:3.5em;font-size:0.8em">
702
- ${renderAvatar({ user, position: 'left' })}
703
- </div>`;
704
- };
705
-
706
700
  export const renderAttachment = (attachment: string): TemplateResult => {
707
701
  const idx = attachment.indexOf(':');
708
702
  const attType = attachment.substr(0, idx);
@@ -881,7 +875,7 @@ export const renderMsgEvent = (
881
875
 
882
876
  ${!isInbound && event.created_by
883
877
  ? html`<div style="margin-left:0.8em;margin-top:0.3em;font-size:0.9em">
884
- ${renderUserAvatar(event.created_by)}
878
+ ${renderAvatar({ user: event.created_by })}
885
879
  </div>`
886
880
  : null}
887
881
  </div>`;
@@ -1016,8 +1010,8 @@ export const renderNoteCreated = (event: TicketEvent): TemplateResult => {
1016
1010
  ></temba-date>
1017
1011
  </div>
1018
1012
  </div>
1019
- <div style="margin-left:0.8em;margin-top:0.3em;font-size:0.8em">
1020
- ${renderUserAvatar(event.created_by)}
1013
+ <div style="margin-left:0.8em;margin-top:0.3em;">
1014
+ ${renderAvatar({ user: event.created_by })}
1021
1015
  </div>
1022
1016
  </div>`;
1023
1017
  };
@@ -0,0 +1,254 @@
1
+ import { css } from 'lit';
2
+
3
+ export const CroppieCSS = css`
4
+ .croppie-container {
5
+ width: 100%;
6
+ height: 100%;
7
+ }
8
+
9
+ .croppie-container .cr-image {
10
+ z-index: -1;
11
+ position: absolute;
12
+ top: 0;
13
+ left: 0;
14
+ transform-origin: 0 0;
15
+ max-height: none;
16
+ max-width: none;
17
+ }
18
+
19
+ .croppie-container .cr-boundary {
20
+ position: relative;
21
+ overflow: hidden;
22
+ margin: 0 auto;
23
+ z-index: 1;
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+
28
+ .croppie-container .cr-viewport,
29
+ .croppie-container .cr-resizer {
30
+ position: absolute;
31
+ border: 2px solid #fff;
32
+ margin: auto;
33
+ top: 0;
34
+ bottom: 0;
35
+ right: 0;
36
+ left: 0;
37
+ box-shadow: 0 0 2000px 2000px rgba(0, 0, 0, 0.5);
38
+ z-index: 0;
39
+ }
40
+
41
+ .croppie-container .cr-resizer {
42
+ z-index: 2;
43
+ box-shadow: none;
44
+ pointer-events: none;
45
+ }
46
+
47
+ .croppie-container .cr-resizer-vertical,
48
+ .croppie-container .cr-resizer-horisontal {
49
+ position: absolute;
50
+ pointer-events: all;
51
+ }
52
+
53
+ .croppie-container .cr-resizer-vertical::after,
54
+ .croppie-container .cr-resizer-horisontal::after {
55
+ display: block;
56
+ position: absolute;
57
+ box-sizing: border-box;
58
+ border: 1px solid black;
59
+ background: #fff;
60
+ width: 10px;
61
+ height: 10px;
62
+ content: '';
63
+ }
64
+
65
+ .croppie-container .cr-resizer-vertical {
66
+ bottom: -5px;
67
+ cursor: row-resize;
68
+ width: 100%;
69
+ height: 10px;
70
+ }
71
+
72
+ .croppie-container .cr-resizer-vertical::after {
73
+ left: 50%;
74
+ margin-left: -5px;
75
+ }
76
+
77
+ .croppie-container .cr-resizer-horisontal {
78
+ right: -5px;
79
+ cursor: col-resize;
80
+ width: 10px;
81
+ height: 100%;
82
+ }
83
+
84
+ .croppie-container .cr-resizer-horisontal::after {
85
+ top: 50%;
86
+ margin-top: -5px;
87
+ }
88
+
89
+ .croppie-container .cr-original-image {
90
+ display: none;
91
+ }
92
+
93
+ .croppie-container .cr-vp-circle {
94
+ border-radius: 50%;
95
+ }
96
+
97
+ .croppie-container .cr-overlay {
98
+ z-index: 1;
99
+ position: absolute;
100
+ cursor: move;
101
+ touch-action: none;
102
+ }
103
+
104
+ .croppie-container .cr-slider-wrap {
105
+ width: 75%;
106
+ margin: 15px auto;
107
+ text-align: center;
108
+ }
109
+
110
+ .croppie-result {
111
+ position: relative;
112
+ overflow: hidden;
113
+ }
114
+
115
+ .croppie-result img {
116
+ position: absolute;
117
+ }
118
+
119
+ .croppie-container .cr-image,
120
+ .croppie-container .cr-overlay,
121
+ .croppie-container .cr-viewport {
122
+ -webkit-transform: translateZ(0);
123
+ -moz-transform: translateZ(0);
124
+ -ms-transform: translateZ(0);
125
+ transform: translateZ(0);
126
+ }
127
+
128
+ /*************************************/
129
+ /***** STYLING RANGE INPUT ***********/
130
+ /*************************************/
131
+ /*http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html */
132
+ /*************************************/
133
+
134
+ .cr-slider {
135
+ -webkit-appearance: none;
136
+ /*removes default webkit styles*/
137
+ /*border: 1px solid white; */ /*fix for FF unable to apply focus style bug */
138
+ width: 300px;
139
+ /*required for proper track sizing in FF*/
140
+ max-width: 100%;
141
+ padding-top: 8px;
142
+ padding-bottom: 8px;
143
+ background-color: transparent;
144
+ }
145
+
146
+ .cr-slider::-webkit-slider-runnable-track {
147
+ width: 100%;
148
+ height: 3px;
149
+ background: rgba(0, 0, 0, 0.5);
150
+ border: 0;
151
+ border-radius: 3px;
152
+ }
153
+
154
+ .cr-slider::-webkit-slider-thumb {
155
+ -webkit-appearance: none;
156
+ border: none;
157
+ height: 16px;
158
+ width: 16px;
159
+ border-radius: 50%;
160
+ background: #ddd;
161
+ margin-top: -6px;
162
+ }
163
+
164
+ .cr-slider:focus {
165
+ outline: none;
166
+ }
167
+ /*
168
+ .cr-slider:focus::-webkit-slider-runnable-track {
169
+ background: #ccc;
170
+ }
171
+ */
172
+
173
+ .cr-slider::-moz-range-track {
174
+ width: 100%;
175
+ height: 3px;
176
+ background: rgba(0, 0, 0, 0.5);
177
+ border: 0;
178
+ border-radius: 3px;
179
+ }
180
+
181
+ .cr-slider::-moz-range-thumb {
182
+ border: none;
183
+ height: 16px;
184
+ width: 16px;
185
+ border-radius: 50%;
186
+ background: #ddd;
187
+ margin-top: -6px;
188
+ }
189
+
190
+ /*hide the outline behind the border*/
191
+ .cr-slider:-moz-focusring {
192
+ outline: 1px solid white;
193
+ outline-offset: -1px;
194
+ }
195
+
196
+ .cr-slider::-ms-track {
197
+ width: 100%;
198
+ height: 5px;
199
+ background: transparent;
200
+ /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
201
+ border-color: transparent; /*leave room for the larger thumb to overflow with a transparent border */
202
+ border-width: 6px 0;
203
+ color: transparent; /*remove default tick marks*/
204
+ }
205
+ .cr-slider::-ms-fill-lower {
206
+ background: rgba(0, 0, 0, 0.5);
207
+ border-radius: 10px;
208
+ }
209
+ .cr-slider::-ms-fill-upper {
210
+ background: rgba(0, 0, 0, 0.5);
211
+ border-radius: 10px;
212
+ }
213
+ .cr-slider::-ms-thumb {
214
+ border: none;
215
+ height: 16px;
216
+ width: 16px;
217
+ border-radius: 50%;
218
+ background: #ddd;
219
+ margin-top: 1px;
220
+ }
221
+ .cr-slider:focus::-ms-fill-lower {
222
+ background: rgba(0, 0, 0, 0.5);
223
+ }
224
+ .cr-slider:focus::-ms-fill-upper {
225
+ background: rgba(0, 0, 0, 0.5);
226
+ }
227
+ /*******************************************/
228
+
229
+ /***********************************/
230
+ /* Rotation Tools */
231
+ /***********************************/
232
+ .cr-rotate-controls {
233
+ position: absolute;
234
+ bottom: 5px;
235
+ left: 5px;
236
+ z-index: 1;
237
+ }
238
+ .cr-rotate-controls button {
239
+ border: 0;
240
+ background: none;
241
+ }
242
+ .cr-rotate-controls i:before {
243
+ display: inline-block;
244
+ font-style: normal;
245
+ font-weight: 900;
246
+ font-size: 22px;
247
+ }
248
+ .cr-rotate-l i:before {
249
+ content: '↺';
250
+ }
251
+ .cr-rotate-r i:before {
252
+ content: '↻';
253
+ }
254
+ `;
@@ -0,0 +1,272 @@
1
+ /* eslint-disable @typescript-eslint/no-this-alias */
2
+ import { html, css, PropertyValueMap } from 'lit';
3
+ import 'croppie/croppie.js';
4
+ import { CroppieCSS } from './CroppieCSS';
5
+ import { property } from 'lit/decorators.js';
6
+ import { Icon } from '../vectoricon';
7
+ import { FormElement } from '../FormElement';
8
+
9
+ export class ImagePicker extends FormElement {
10
+ static styles = css`
11
+ ${CroppieCSS}
12
+
13
+ .croppie {
14
+ max-width: 400px;
15
+ border: 0px solid #ccc;
16
+ border-radius: 0.5em;
17
+ overflow: hidden;
18
+ background: #fff;
19
+ margin-top: -20%;
20
+ box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.1);
21
+ }
22
+
23
+ .croppie .controls {
24
+ display: flex;
25
+ align-items: center;
26
+ flex-direction: row;
27
+ justify-content: center;
28
+ position: absolute;
29
+ z-index: 1;
30
+ width: 400px;
31
+ margin-top: -42px;
32
+ }
33
+
34
+ .toggle {
35
+ height: 110px;
36
+ width: 110px;
37
+ cursor: pointer;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ }
42
+
43
+ .circle .toggle {
44
+ border-radius: 50%;
45
+ }
46
+
47
+ .toggle.set {
48
+ box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
49
+ rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.1);
50
+ }
51
+
52
+ .toggle.set:hover {
53
+ box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
54
+ rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.2);
55
+ }
56
+
57
+ .toggle temba-icon {
58
+ color: rgba(0, 0, 0, 0.2);
59
+ padding: 5px;
60
+ }
61
+
62
+ toggle:hover temba-icon {
63
+ color: rgba(0, 0, 0, 0.8);
64
+ }
65
+
66
+ .toggle.set temba-icon {
67
+ border-radius: 50%;
68
+ margin-right: -90%;
69
+ margin-bottom: -50%;
70
+ background: rgba(240, 240, 240, 1);
71
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px 0px;
72
+ }
73
+
74
+ .toggle.set:hover temba-icon {
75
+ background: #fff;
76
+ color: var(--color-primary-dark);
77
+ }
78
+
79
+ .circle .toggle.set temba-icon {
80
+ margin-right: -70%;
81
+ margin-bottom: -70%;
82
+ }
83
+
84
+ .hidden {
85
+ display: none;
86
+ }
87
+
88
+ .controls temba-icon {
89
+ margin: 0em 0.75em;
90
+ background: rgba(255, 255, 255, 0.8);
91
+ border-radius: 50%;
92
+ padding: 6px;
93
+ transition: all 0.1s ease-in-out;
94
+ }
95
+
96
+ .controls {
97
+ pointer-events: none;
98
+ display: flex;
99
+ }
100
+
101
+ .controls temba-icon {
102
+ pointer-events: all;
103
+ }
104
+
105
+ .controls temba-icon.close {
106
+ color: rgba(0, 0, 0, 0.2);
107
+ background: rgba(255, 255, 255, 0.2);
108
+ }
109
+
110
+ .controls temba-icon.submit {
111
+ color: rgba(0, 0, 0, 0.2);
112
+ box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);
113
+ }
114
+
115
+ .controls temba-icon:hover {
116
+ color: white;
117
+ cursor: pointer;
118
+ background: var(--color-primary-dark);
119
+ }
120
+ `;
121
+
122
+ @property({ type: String })
123
+ tempImage: string;
124
+
125
+ @property({ type: String })
126
+ url: string;
127
+
128
+ @property({ type: String })
129
+ shape = 'square';
130
+
131
+ @property({ type: Boolean, attribute: false })
132
+ showCroppie = false;
133
+
134
+ uploadReader = new FileReader();
135
+ croppie: any;
136
+
137
+ protected firstUpdated(
138
+ changed: PropertyValueMap<any> | Map<PropertyKey, unknown>
139
+ ): void {
140
+ super.firstUpdated(changed);
141
+ const picker = this;
142
+
143
+ this.uploadReader.onload = function () {
144
+ picker.launchCroppie(picker.uploadReader.result as any);
145
+ };
146
+ }
147
+
148
+ public updated(changed: Map<string, any>): void {
149
+ super.updated(changed);
150
+ if (changed.has('url')) {
151
+ this.setAttribute('url', this.url);
152
+ }
153
+ }
154
+
155
+ private closeCroppie() {
156
+ this.showCroppie = false;
157
+ const wrapper = this.shadowRoot.querySelector('.croppie .embed');
158
+ if (wrapper.firstChild) {
159
+ wrapper.removeChild(wrapper.firstChild);
160
+ }
161
+ }
162
+
163
+ private launchCroppie(url: string) {
164
+ const wrapper = this.shadowRoot.querySelector('.croppie .embed');
165
+ if (wrapper.firstChild) {
166
+ wrapper.removeChild(wrapper.firstChild);
167
+ }
168
+ this.showCroppie = true;
169
+ const croppie = document.createElement('div');
170
+ wrapper.appendChild(croppie);
171
+
172
+ const Croppie = (window as any).Croppie;
173
+ this.croppie = new Croppie(croppie, {
174
+ enableExif: true,
175
+ viewport: {
176
+ width: 300,
177
+ height: 300,
178
+ type: this.shape,
179
+ },
180
+ boundary: {
181
+ width: 400,
182
+ height: 400,
183
+ },
184
+ });
185
+
186
+ this.croppie.bind({ url });
187
+ }
188
+
189
+ private saveResult() {
190
+ const picker = this;
191
+ this.croppie
192
+ .result({
193
+ type: 'blob',
194
+ size: 'viewport',
195
+ format: 'webp',
196
+ quality: 1,
197
+ circle: false,
198
+ })
199
+ .then(function (resp: any) {
200
+ const blob = resp;
201
+ picker.url = URL.createObjectURL(blob);
202
+
203
+ // const blob = dataURItoBlob(resp);
204
+ const fd = new FormData();
205
+ fd.append(picker.name, blob, 'filename.webp');
206
+
207
+ picker.value = fd;
208
+ picker.closeCroppie();
209
+
210
+ console.log('image changed', picker.name, picker.value, picker.url);
211
+ // picker.dispatchEvent(new CustomEvent('image-changed', { detail: resp }));
212
+ });
213
+ }
214
+
215
+ private handleToggleClicked() {
216
+ const fileInput = this.shadowRoot.querySelector('#file');
217
+ (fileInput as any).click();
218
+ }
219
+
220
+ private handleFileChanged(e: Event) {
221
+ const input = e.target as any;
222
+ if (input.files.length > 0) {
223
+ this.uploadReader.readAsDataURL(input.files[0]);
224
+ }
225
+ input.value = '';
226
+ }
227
+
228
+ protected render() {
229
+ return html`
230
+ <div class="wrapper ${this.shape} ${this.label ? 'label' : ''}">
231
+ <temba-field
232
+ name=${this.name}
233
+ label=${this.label}
234
+ .helpText=${this.helpText}
235
+ .errors=${this.errors}
236
+ .widgetOnly=${this.widgetOnly}
237
+ .helpAlways=${true}
238
+ ?disabled=${this.disabled}
239
+ >
240
+ <input class='hidden' type="file" accept="image/*" capture="camera" id="file" name="file" @change=${
241
+ this.handleFileChanged
242
+ }/>
243
+ <div class='toggle ${this.url ? 'set' : ''} ${
244
+ this.showCroppie ? 'hidden' : ''
245
+ }' @click=${this.handleToggleClicked} style="background: ${
246
+ this.url
247
+ ? `url('${this.url}') center / contain no-repeat`
248
+ : 'rgba(0, 0, 0, 0.1)'
249
+ }">
250
+ <temba-icon name=${Icon.upload_image} size="1.5"></temba-icon>
251
+ </div>
252
+
253
+ <temba-mask ?show=${this.showCroppie} class="${
254
+ this.showCroppie ? 'editing' : ''
255
+ }">
256
+ <div class='croppie'>
257
+ <div class='embed'></div>
258
+ <div class='controls'>
259
+ <temba-icon class="close" size="1" name=${Icon.close} @click=${
260
+ this.closeCroppie
261
+ }></temba-icon>
262
+ <div style="flex-grow:1"></div>
263
+ <temba-icon class="submit" size="1" name=${Icon.submit} @click=${
264
+ this.saveResult
265
+ }></temba-icon>
266
+ </div>
267
+ </temba-mask>
268
+ </temba-field>
269
+ </div>
270
+ `;
271
+ }
272
+ }
package/src/interfaces.ts CHANGED
@@ -55,6 +55,7 @@ export interface User {
55
55
  email?: string;
56
56
  role?: string;
57
57
  created_on?: string;
58
+ avatar?: string;
58
59
  }
59
60
 
60
61
  export interface Ticket {
@@ -66,7 +67,7 @@ export interface Ticket {
66
67
  status: string;
67
68
  contact: ObjectReference;
68
69
  topic: ObjectReference;
69
- assignee?: { email: string; name: string };
70
+ assignee?: { email: string; name: string; avatar?: string };
70
71
  }
71
72
 
72
73
  export interface FlowResult {
@@ -95,6 +95,9 @@ export class NotificationList extends TembaList {
95
95
  } else if (notification.export.type === 'results') {
96
96
  icon = Icon.results_export;
97
97
  body = 'Exported flow results';
98
+ } else if (notification.export.type === 'ticket') {
99
+ icon = Icon.tickets_export;
100
+ body = 'Exported tickets';
98
101
  }
99
102
  } else if (notification.type === 'tickets:activity') {
100
103
  icon = Icon.tickets;
@@ -993,7 +993,7 @@ export class TembaMenu extends RapidElement {
993
993
  });
994
994
 
995
995
  if (menuItem.avatar) {
996
- icon = renderAvatar({ name: menuItem.avatar, tip: false });
996
+ icon = renderAvatar({ name: menuItem.avatar, tip: false, scale: 1.2 });
997
997
  if (menuItem.bubble) {
998
998
  icon = html`${icon}${menuItem.bubble
999
999
  ? html`<div