@nyaruka/temba-components 0.67.4 → 0.70.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 (83) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/demo/index.html +5 -3
  3. package/dist/{a386634d.js → 2102a010.js} +862 -120
  4. package/dist/index.js +862 -120
  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/ContactHistory.js +20 -13
  11. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  12. package/out-tsc/src/contacts/ContactTickets.js +27 -25
  13. package/out-tsc/src/contacts/ContactTickets.js.map +1 -1
  14. package/out-tsc/src/contacts/events.js +7 -13
  15. package/out-tsc/src/contacts/events.js.map +1 -1
  16. package/out-tsc/src/imagepicker/CroppieCSS.js +254 -0
  17. package/out-tsc/src/imagepicker/CroppieCSS.js.map +1 -0
  18. package/out-tsc/src/imagepicker/ImagePicker.js +246 -0
  19. package/out-tsc/src/imagepicker/ImagePicker.js.map +1 -0
  20. package/out-tsc/src/interfaces.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 +15 -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/store/Store.js +3 -0
  28. package/out-tsc/src/store/Store.js.map +1 -1
  29. package/out-tsc/src/store/StoreElement.js +11 -30
  30. package/out-tsc/src/store/StoreElement.js.map +1 -1
  31. package/out-tsc/src/store/StoreMonitorElement.js +50 -0
  32. package/out-tsc/src/store/StoreMonitorElement.js.map +1 -0
  33. package/out-tsc/src/user/TembaUser.js +107 -0
  34. package/out-tsc/src/user/TembaUser.js.map +1 -0
  35. package/out-tsc/src/utils/index.js +25 -17
  36. package/out-tsc/src/utils/index.js.map +1 -1
  37. package/out-tsc/src/vectoricon/index.js +4 -1
  38. package/out-tsc/src/vectoricon/index.js.map +1 -1
  39. package/out-tsc/src/webchat/WebChat.js +515 -0
  40. package/out-tsc/src/webchat/WebChat.js.map +1 -0
  41. package/out-tsc/src/webchat/index.js +7 -0
  42. package/out-tsc/src/webchat/index.js.map +1 -0
  43. package/out-tsc/temba-modules.js +8 -0
  44. package/out-tsc/temba-modules.js.map +1 -1
  45. package/out-tsc/test/temba-contact-tickets.test.js +1 -1
  46. package/out-tsc/test/temba-contact-tickets.test.js.map +1 -1
  47. package/package.json +4 -2
  48. package/screenshots/truth/contacts/tickets-assignment.png +0 -0
  49. package/screenshots/truth/contacts/tickets.png +0 -0
  50. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  51. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  52. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  53. package/screenshots/truth/menu/menu-root.png +0 -0
  54. package/screenshots/truth/menu/menu-submenu.png +0 -0
  55. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  56. package/screenshots/truth/menu/menu-tasks.png +0 -0
  57. package/src/contacts/ContactHistory.ts +28 -14
  58. package/src/contacts/ContactTickets.ts +28 -36
  59. package/src/contacts/events.ts +8 -25
  60. package/src/imagepicker/CroppieCSS.ts +254 -0
  61. package/src/imagepicker/ImagePicker.ts +269 -0
  62. package/src/interfaces.ts +2 -1
  63. package/src/list/TembaMenu.ts +1 -1
  64. package/src/list/TicketList.ts +15 -12
  65. package/src/mask/Mask.ts +32 -0
  66. package/src/store/Store.ts +4 -0
  67. package/src/store/StoreElement.ts +13 -38
  68. package/src/store/StoreMonitorElement.ts +61 -0
  69. package/src/untyped.d.ts +1 -0
  70. package/src/user/TembaUser.ts +111 -0
  71. package/src/utils/index.ts +26 -19
  72. package/src/vectoricon/index.ts +4 -1
  73. package/src/webchat/WebChat.ts +559 -0
  74. package/src/webchat/index.ts +6 -0
  75. package/static/svg/index.svg +1 -1
  76. package/static/svg/webchat.svg +1 -0
  77. package/static/svg/work/traced/camera-01.svg +1 -0
  78. package/static/svg/work/traced/send-03.svg +1 -0
  79. package/static/svg/work/used/camera-01.svg +4 -0
  80. package/static/svg/work/used/send-03.svg +3 -0
  81. package/svg.js +12 -8
  82. package/temba-modules.ts +8 -0
  83. package/test/temba-contact-tickets.test.ts +1 -3
@@ -1 +1 @@
1
- {"version":3,"file":"temba-contact-tickets.test.js","sourceRoot":"","sources":["../../test/temba-contact-tickets.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EACL,gBAAgB,EAChB,OAAO,EACP,YAAY,EACZ,SAAS,EACT,OAAO,EACP,OAAO,GACR,MAAM,cAAc,CAAC;AAEtB,MAAM,GAAG,GAAG,uBAAuB,CAAC;AACpC,MAAM,iBAAiB,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE,EAAE;IAClD,MAAM,cAAc,GAAG,CAAC,MAAM,YAAY,CACxC,GAAG,EACH,KAAK,EACL,EAAE,EACF,GAAG,CACJ,CAAmB,CAAC;IACrB,+BAA+B;IAC/B,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAEF,OAAO,CAAC,+BAA+B,CAAC,CAAC;AACzC,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,CACL,uEAAuE,EACvE,4CAA4C,CAC7C,CAAC;QACF,SAAS,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,OAAO,GAAmB,MAAM,iBAAiB,CAAC;YACtD,OAAO,EAAE,sCAAsC;YAC/C,KAAK,EAAE,oBAAoB;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC3C,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,OAAO,GAAmB,MAAM,iBAAiB,CAAC;YACtD,OAAO,EAAE,sCAAsC;YAC/C,KAAK,EAAE,oBAAoB;SAC5B,CAAC,CAAC;QAEH,8BAA8B;QAE5B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,gBAAgB,CAClD,CAAC,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC3C,MAAM,gBAAgB,CAAC,6BAA6B,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { assert, waitUntil } from '@open-wc/testing';\nimport { ContactTickets } from '../src/contacts/ContactTickets';\nimport {\n assertScreenshot,\n getClip,\n getComponent,\n loadStore,\n mockGET,\n mockNow,\n} from './utils.test';\n\nconst TAG = 'temba-contact-tickets';\nconst getContactTickets = async (attrs: any = {}) => {\n const contactTickets = (await getComponent(\n TAG,\n attrs,\n '',\n 400\n )) as ContactTickets;\n // wait for our contact to load\n await waitUntil(() => !!contactTickets.data);\n return contactTickets;\n};\n\nmockNow('2023-04-07T00:00:00.000-00:00');\ndescribe('temba-contact-tickets', () => {\n beforeEach(() => {\n mockGET(\n /\\/api\\/v2\\/tickets.json\\?contact=24d64810-3315-4ff5-be85-48e3fe055bf9/,\n '/test-assets/contacts/contact-tickets.json'\n );\n loadStore();\n });\n\n it('renders default', async () => {\n const tickets: ContactTickets = await getContactTickets({\n contact: '24d64810-3315-4ff5-be85-48e3fe055bf9',\n agent: 'admin1@nyaruka.com',\n });\n assert.instanceOf(tickets, ContactTickets);\n await assertScreenshot('contacts/tickets', getClip(tickets));\n });\n\n it('shows assignment picker', async () => {\n const tickets: ContactTickets = await getContactTickets({\n contact: '24d64810-3315-4ff5-be85-48e3fe055bf9',\n agent: 'admin1@nyaruka.com',\n });\n\n // click on the avatar element\n (\n tickets.shadowRoot.querySelector('.avatar-circle') as HTMLDivElement\n ).click();\n assert.instanceOf(tickets, ContactTickets);\n await assertScreenshot('contacts/tickets-assignment', getClip(tickets));\n });\n});\n"]}
1
+ {"version":3,"file":"temba-contact-tickets.test.js","sourceRoot":"","sources":["../../test/temba-contact-tickets.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EACL,gBAAgB,EAChB,OAAO,EACP,YAAY,EACZ,SAAS,EACT,OAAO,EACP,OAAO,GACR,MAAM,cAAc,CAAC;AAEtB,MAAM,GAAG,GAAG,uBAAuB,CAAC;AACpC,MAAM,iBAAiB,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE,EAAE;IAClD,MAAM,cAAc,GAAG,CAAC,MAAM,YAAY,CACxC,GAAG,EACH,KAAK,EACL,EAAE,EACF,GAAG,CACJ,CAAmB,CAAC;IACrB,+BAA+B;IAC/B,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAEF,OAAO,CAAC,+BAA+B,CAAC,CAAC;AACzC,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,CACL,uEAAuE,EACvE,4CAA4C,CAC7C,CAAC;QACF,SAAS,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,OAAO,GAAmB,MAAM,iBAAiB,CAAC;YACtD,OAAO,EAAE,sCAAsC;YAC/C,KAAK,EAAE,oBAAoB;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC3C,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,OAAO,GAAmB,MAAM,iBAAiB,CAAC;YACtD,OAAO,EAAE,sCAAsC;YAC/C,KAAK,EAAE,oBAAoB;SAC5B,CAAC,CAAC;QAEH,8BAA8B;QAC7B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,YAAY,CAAoB,CAAC,KAAK,EAAE,CAAC;QAC3E,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC3C,MAAM,gBAAgB,CAAC,6BAA6B,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { assert, waitUntil } from '@open-wc/testing';\nimport { ContactTickets } from '../src/contacts/ContactTickets';\nimport {\n assertScreenshot,\n getClip,\n getComponent,\n loadStore,\n mockGET,\n mockNow,\n} from './utils.test';\n\nconst TAG = 'temba-contact-tickets';\nconst getContactTickets = async (attrs: any = {}) => {\n const contactTickets = (await getComponent(\n TAG,\n attrs,\n '',\n 400\n )) as ContactTickets;\n // wait for our contact to load\n await waitUntil(() => !!contactTickets.data);\n return contactTickets;\n};\n\nmockNow('2023-04-07T00:00:00.000-00:00');\ndescribe('temba-contact-tickets', () => {\n beforeEach(() => {\n mockGET(\n /\\/api\\/v2\\/tickets.json\\?contact=24d64810-3315-4ff5-be85-48e3fe055bf9/,\n '/test-assets/contacts/contact-tickets.json'\n );\n loadStore();\n });\n\n it('renders default', async () => {\n const tickets: ContactTickets = await getContactTickets({\n contact: '24d64810-3315-4ff5-be85-48e3fe055bf9',\n agent: 'admin1@nyaruka.com',\n });\n assert.instanceOf(tickets, ContactTickets);\n await assertScreenshot('contacts/tickets', getClip(tickets));\n });\n\n it('shows assignment picker', async () => {\n const tickets: ContactTickets = await getContactTickets({\n contact: '24d64810-3315-4ff5-be85-48e3fe055bf9',\n agent: 'admin1@nyaruka.com',\n });\n\n // click on the avatar element\n (tickets.shadowRoot.querySelector('temba-user') as HTMLDivElement).click();\n assert.instanceOf(tickets, ContactTickets);\n await assertScreenshot('contacts/tickets-assignment', getClip(tickets));\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.67.4",
3
+ "version": "0.70.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",
@@ -1,7 +1,7 @@
1
1
  import { css } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { html, TemplateResult } from 'lit-html';
4
- import { Contact, CustomEventType, Ticket } from '../interfaces';
4
+ import { Contact, CustomEventType, Msg, Ticket } from '../interfaces';
5
5
  import { RapidElement } from '../RapidElement';
6
6
  import { Asset, getAssets, getClasses, postJSON, throttle } from '../utils';
7
7
 
@@ -58,6 +58,7 @@ import {
58
58
  SCROLL_THRESHOLD,
59
59
  } from './helpers';
60
60
  import { Lightbox } from '../lightbox/Lightbox';
61
+ import { Store } from '../store/Store';
61
62
 
62
63
  // when images load, make sure we are on the bottom of the scroll window if necessary
63
64
  export const loadHandler = function (event) {
@@ -77,6 +78,7 @@ export const loadHandler = function (event) {
77
78
 
78
79
  export class ContactHistory extends RapidElement {
79
80
  public httpComplete: Promise<void | ContactHistoryPage>;
81
+ private store: Store;
80
82
 
81
83
  public constructor() {
82
84
  super();
@@ -85,6 +87,7 @@ export class ContactHistory extends RapidElement {
85
87
  connectedCallback() {
86
88
  super.connectedCallback();
87
89
  this.shadowRoot.addEventListener('load', loadHandler, true);
90
+ this.store = document.querySelector('temba-store') as Store;
88
91
  }
89
92
 
90
93
  disconnectedCallback() {
@@ -632,7 +635,13 @@ export class ContactHistory extends RapidElement {
632
635
  case Events.MESSAGE_CREATED:
633
636
  case Events.MESSAGE_RECEIVED:
634
637
  case Events.BROADCAST_CREATED:
635
- return renderMsgEvent(event as MsgEvent, this.agent);
638
+ if ((event as MsgEvent).created_by) {
639
+ (event as MsgEvent).created_by = this.store.getUser(
640
+ (event as MsgEvent).created_by.email
641
+ );
642
+ }
643
+
644
+ return renderMsgEvent(event as MsgEvent);
636
645
 
637
646
  case Events.FLOW_ENTERED:
638
647
  case Events.FLOW_EXITED:
@@ -840,18 +849,23 @@ export class ContactHistory extends RapidElement {
840
849
  })}
841
850
  </div>
842
851
 
843
- <div class="new-messages-container">
844
- <div
845
- @click=${() => {
846
- this.scrollToBottom(true);
847
- }}
848
- class="new-messages ${getClasses({
849
- expanded: this.showMessageAlert,
850
- })}"
851
- >
852
- New Messages
853
- </div>
854
- </div>
852
+ ${
853
+ this.contact && this.contact.status === 'active'
854
+ ? html`<div class="new-messages-container">
855
+ <div
856
+ @click=${() => {
857
+ this.scrollToBottom(true);
858
+ }}
859
+ class="new-messages ${getClasses({
860
+ expanded: this.showMessageAlert,
861
+ })}"
862
+ >
863
+ New Messages
864
+ </div>
865
+ </div>`
866
+ : null
867
+ }
868
+
855
869
  </div>
856
870
  `;
857
871
  }
@@ -2,15 +2,12 @@ import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { CustomEventType, Ticket, TicketStatus, User } from '../interfaces';
4
4
  import { StoreElement } from '../store/StoreElement';
5
- import {
6
- getClasses,
7
- getFullName,
8
- postJSON,
9
- renderAvatar,
10
- stopEvent,
11
- } from '../utils';
5
+ import { getClasses, postJSON, stopEvent } from '../utils';
12
6
  import { Icon } from '../vectoricon';
13
7
 
8
+ const dropdownUserScale = 0.7;
9
+ const inlineUserScale = 0.8;
10
+
14
11
  export class ContactTickets extends StoreElement {
15
12
  @property({ type: String })
16
13
  agent: string;
@@ -129,6 +126,10 @@ export class ContactTickets extends StoreElement {
129
126
  border-bottom: 1px solid #f3f3f3;
130
127
  }
131
128
 
129
+ .option-group temba-user {
130
+ flex-grow: 1;
131
+ }
132
+
132
133
  .assigned .user {
133
134
  flex-grow: 1;
134
135
  }
@@ -155,7 +156,6 @@ export class ContactTickets extends StoreElement {
155
156
 
156
157
  .user {
157
158
  display: flex;
158
- padding: 0.4em 0.7em;
159
159
  align-items: center;
160
160
  border-radius: var(--curvature);
161
161
  cursor: pointer;
@@ -165,11 +165,6 @@ export class ContactTickets extends StoreElement {
165
165
  background: var(--color-selection);
166
166
  }
167
167
 
168
- .user .avatar {
169
- font-size: 0.5em;
170
- margin-right: 1em;
171
- }
172
-
173
168
  .user .name {
174
169
  display: -webkit-box;
175
170
  -webkit-line-clamp: 1;
@@ -246,16 +241,6 @@ export class ContactTickets extends StoreElement {
246
241
  }
247
242
  }
248
243
 
249
- private renderUser(user: User) {
250
- if (!user) {
251
- return null;
252
- }
253
- return html`<div class="user">
254
- <div class="avatar">${renderAvatar({ user: user })}</div>
255
- <div class="name">${getFullName(user)}</div>
256
- </div>`;
257
- }
258
-
259
244
  private handleClose(uuid: string) {
260
245
  postJSON(`/api/v2/ticket_actions.json`, {
261
246
  tickets: [uuid],
@@ -288,6 +273,7 @@ export class ContactTickets extends StoreElement {
288
273
  if (ticket.assignee && ticket.assignee.email === email) {
289
274
  return;
290
275
  }
276
+ this.blur();
291
277
 
292
278
  postJSON(`/api/v2/ticket_actions.json`, {
293
279
  tickets: [uuid],
@@ -355,12 +341,10 @@ export class ContactTickets extends StoreElement {
355
341
  <div slot="toggle" class="toggle">
356
342
  ${ticket.assignee
357
343
  ? html`
358
- <div style="font-size:0.5em">
359
- ${renderAvatar({
360
- name: ticket.assignee.name,
361
- position: 'left',
362
- })}
363
- </div>
344
+ <temba-user
345
+ email=${ticket.assignee.email}
346
+ scale="${inlineUserScale}"
347
+ ></temba-user>
364
348
  `
365
349
  : html`
366
350
  <temba-button
@@ -386,11 +370,11 @@ export class ContactTickets extends StoreElement {
386
370
  ? 'current-user'
387
371
  : ''}"
388
372
  >
389
- ${this.renderUser(
390
- users.find(
391
- user => user.email === ticket.assignee.email
392
- )
393
- )}
373
+ <temba-user
374
+ email=${ticket.assignee.email}
375
+ name
376
+ scale="${dropdownUserScale}"
377
+ ></temba-user>
394
378
  <temba-button
395
379
  name="Unassign"
396
380
  primary
@@ -420,7 +404,11 @@ export class ContactTickets extends StoreElement {
420
404
  );
421
405
  }}
422
406
  >
423
- ${this.renderUser(agent)}
407
+ <temba-user
408
+ email=${agent.email}
409
+ name
410
+ scale="${dropdownUserScale}"
411
+ ></temba-user>
424
412
  </div>
425
413
  `
426
414
  : null}
@@ -446,7 +434,11 @@ export class ContactTickets extends StoreElement {
446
434
  );
447
435
  }}
448
436
  >
449
- ${this.renderUser(user)}
437
+ <temba-user
438
+ email=${user.email}
439
+ scale="${dropdownUserScale}"
440
+ name
441
+ ></temba-user>
450
442
  </div>`;
451
443
  })}
452
444
  </div>
@@ -1,13 +1,6 @@
1
- import { css, EventPart, html, TemplateResult } from 'lit';
1
+ import { css, html, TemplateResult } from 'lit';
2
2
  import { Msg, ObjectReference, User } from '../interfaces';
3
- import {
4
- getClasses,
5
- NamedObject,
6
- oxford,
7
- oxfordFn,
8
- oxfordNamed,
9
- renderAvatar,
10
- } from '../utils';
3
+ import { getClasses, oxford, oxfordFn, oxfordNamed } from '../utils';
11
4
  import { Icon } from '../vectoricon';
12
5
  import { getDisplayName } from './helpers';
13
6
 
@@ -697,12 +690,6 @@ export const getEventGroupType = (event: ContactEvent, ticket: string) => {
697
690
  return 'verbose';
698
691
  };
699
692
 
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
693
  export const renderAttachment = (attachment: string): TemplateResult => {
707
694
  const idx = attachment.indexOf(':');
708
695
  const attType = attachment.substr(0, idx);
@@ -764,10 +751,7 @@ export const renderAttachment = (attachment: string): TemplateResult => {
764
751
  return html`<div style="">${inner}</div>`;
765
752
  };
766
753
 
767
- export const renderMsgEvent = (
768
- event: MsgEvent,
769
- agent: string
770
- ): TemplateResult => {
754
+ export const renderMsgEvent = (event: MsgEvent): TemplateResult => {
771
755
  const isInbound = event.type === Events.MESSAGE_RECEIVED;
772
756
  const isError = event.status === 'E';
773
757
  const isFailure = event.status === 'F';
@@ -880,9 +864,10 @@ export const renderMsgEvent = (
880
864
  </div>
881
865
 
882
866
  ${!isInbound && event.created_by
883
- ? html`<div style="margin-left:0.8em;margin-top:0.3em;font-size:0.9em">
884
- ${renderUserAvatar(event.created_by)}
885
- </div>`
867
+ ? html`<temba-user
868
+ style="margin-left:0.5em"
869
+ email=${event.created_by.email}
870
+ ></temba-user>`
886
871
  : null}
887
872
  </div>`;
888
873
  };
@@ -1016,9 +1001,7 @@ export const renderNoteCreated = (event: TicketEvent): TemplateResult => {
1016
1001
  ></temba-date>
1017
1002
  </div>
1018
1003
  </div>
1019
- <div style="margin-left:0.8em;margin-top:0.3em;font-size:0.8em">
1020
- ${renderUserAvatar(event.created_by)}
1021
- </div>
1004
+ <temba-user email=${event.created_by.email}></temba-user>
1022
1005
  </div>`;
1023
1006
  };
1024
1007
 
@@ -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
+ `;