@nyaruka/temba-components 0.26.6 → 0.26.9

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 (106) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/demo/index.html +9 -1
  3. package/dist/{29fc0c7c.js → d0cc86be.js} +403 -56
  4. package/dist/index.js +403 -56
  5. package/dist/static/icons/symbol-defs.svg +13 -1
  6. package/dist/static/img/schemes/email.svg +1 -0
  7. package/dist/static/img/schemes/facebook.svg +1 -0
  8. package/dist/static/img/schemes/instagram.svg +1 -0
  9. package/dist/static/img/schemes/line.svg +1 -0
  10. package/dist/static/img/schemes/messenger.svg +1 -0
  11. package/dist/static/img/schemes/tel.svg +34 -0
  12. package/dist/static/img/schemes/telegram.svg +1 -0
  13. package/dist/static/img/schemes/twitter.svg +1 -0
  14. package/dist/static/img/schemes/viber.svg +1 -0
  15. package/dist/static/img/schemes/vk.svg +1 -0
  16. package/dist/static/img/schemes/whatsapp.svg +1 -0
  17. package/dist/sw.js +1 -1
  18. package/dist/sw.js.map +1 -1
  19. package/dist/templates/components-body.html +1 -1
  20. package/dist/templates/components-head.html +1 -1
  21. package/out-tsc/src/RapidElement.js.map +1 -1
  22. package/out-tsc/src/RefreshElement.js +28 -0
  23. package/out-tsc/src/RefreshElement.js.map +1 -0
  24. package/out-tsc/src/button/Button.js +4 -0
  25. package/out-tsc/src/button/Button.js.map +1 -1
  26. package/out-tsc/src/contacts/ContactChat.js +5 -13
  27. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  28. package/out-tsc/src/contacts/ContactFieldEditor.js +199 -0
  29. package/out-tsc/src/contacts/ContactFieldEditor.js.map +1 -0
  30. package/out-tsc/src/contacts/ContactFields.js +106 -0
  31. package/out-tsc/src/contacts/ContactFields.js.map +1 -0
  32. package/out-tsc/src/contacts/ContactGroups.js +39 -0
  33. package/out-tsc/src/contacts/ContactGroups.js.map +1 -0
  34. package/out-tsc/src/contacts/ContactName.js +40 -0
  35. package/out-tsc/src/contacts/ContactName.js.map +1 -0
  36. package/out-tsc/src/contacts/ContactStoreElement.js +44 -0
  37. package/out-tsc/src/contacts/ContactStoreElement.js.map +1 -0
  38. package/out-tsc/src/contacts/ContactUrn.js +38 -0
  39. package/out-tsc/src/contacts/ContactUrn.js.map +1 -0
  40. package/out-tsc/src/contacts/events.js +76 -30
  41. package/out-tsc/src/contacts/events.js.map +1 -1
  42. package/out-tsc/src/contactsearch/ContactSearch.js +1 -1
  43. package/out-tsc/src/contactsearch/ContactSearch.js.map +1 -1
  44. package/out-tsc/src/interfaces.js +1 -0
  45. package/out-tsc/src/interfaces.js.map +1 -1
  46. package/out-tsc/src/label/Label.js +32 -12
  47. package/out-tsc/src/label/Label.js.map +1 -1
  48. package/out-tsc/src/select/Select.js +4 -4
  49. package/out-tsc/src/select/Select.js.map +1 -1
  50. package/out-tsc/src/store/Store.js +97 -3
  51. package/out-tsc/src/store/Store.js.map +1 -1
  52. package/out-tsc/src/store/StoreElement.js +55 -0
  53. package/out-tsc/src/store/StoreElement.js.map +1 -0
  54. package/out-tsc/src/textinput/TextInput.js +35 -17
  55. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  56. package/out-tsc/src/vectoricon/VectorIcon.js +16 -14
  57. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  58. package/out-tsc/temba-modules.js +12 -0
  59. package/out-tsc/temba-modules.js.map +1 -1
  60. package/out-tsc/test/temba-contact-history.test.js +1 -1
  61. package/out-tsc/test/temba-contact-history.test.js.map +1 -1
  62. package/package.json +4 -5
  63. package/rollup.config.js +1 -0
  64. package/screenshots/truth/contacts/history.png +0 -0
  65. package/src/RapidElement.ts +0 -1
  66. package/src/RefreshElement.ts +33 -0
  67. package/src/button/Button.ts +4 -0
  68. package/src/contacts/ContactChat.ts +7 -16
  69. package/src/contacts/ContactFieldEditor.ts +201 -0
  70. package/src/contacts/ContactFields.ts +112 -0
  71. package/src/contacts/ContactGroups.ts +41 -0
  72. package/src/contacts/ContactName.ts +37 -0
  73. package/src/contacts/ContactStoreElement.ts +51 -0
  74. package/src/contacts/ContactUrn.ts +38 -0
  75. package/src/contacts/events.ts +83 -30
  76. package/src/contactsearch/ContactSearch.ts +1 -1
  77. package/src/interfaces.ts +2 -0
  78. package/src/label/Label.ts +30 -7
  79. package/src/select/Select.ts +4 -4
  80. package/src/store/Store.ts +124 -3
  81. package/src/store/StoreElement.ts +71 -0
  82. package/src/textinput/TextInput.ts +48 -27
  83. package/src/vectoricon/VectorIcon.ts +19 -14
  84. package/static/icons/Read Me.txt +1 -1
  85. package/static/icons/SVG/calendar1.svg +5 -0
  86. package/static/icons/SVG/corner-down-left.svg +5 -0
  87. package/static/icons/SVG/more-horizontal.svg +5 -0
  88. package/static/icons/SVG/refresh-cw.svg +5 -0
  89. package/static/icons/demo-external-svg.html +21 -1
  90. package/static/icons/demo.html +34 -2
  91. package/static/icons/selection.json +412 -316
  92. package/static/icons/symbol-defs.svg +13 -1
  93. package/static/img/schemes/email.svg +1 -0
  94. package/static/img/schemes/facebook.svg +1 -0
  95. package/static/img/schemes/instagram.svg +1 -0
  96. package/static/img/schemes/line.svg +1 -0
  97. package/static/img/schemes/messenger.svg +1 -0
  98. package/static/img/schemes/tel.svg +34 -0
  99. package/static/img/schemes/telegram.svg +1 -0
  100. package/static/img/schemes/twitter.svg +1 -0
  101. package/static/img/schemes/viber.svg +1 -0
  102. package/static/img/schemes/vk.svg +1 -0
  103. package/static/img/schemes/whatsapp.svg +1 -0
  104. package/temba-modules.ts +12 -0
  105. package/test/temba-contact-history.test.ts +1 -1
  106. package/test-assets/contacts/history.json +5 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactFieldEditor.js","sourceRoot":"","sources":["../../../src/contacts/ContactFieldEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators';\nimport { RapidElement } from '../RapidElement';\nimport { TextInput } from '../textinput/TextInput';\n\nexport class ContactFieldEditor extends RapidElement {\n @property({ type: String })\n key: string;\n\n @property({ type: String })\n value: string;\n\n @property({ type: String })\n name: string;\n\n @property({ type: String })\n type: string;\n\n @property({ type: String })\n icon = navigator.clipboard ? 'copy' : '';\n\n @property({ type: String })\n iconClass = '';\n\n static get styles() {\n return css`\n .prefix {\n background: rgba(0, 0, 0, 0.05);\n border-top-left-radius: 4px;\n border-bottom-left-radius: 4px;\n color: #888;\n cursor: pointer;\n width: 100px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: flex;\n padding: 0em 0.5em;\n }\n\n .prefix .name {\n padding: 0.5em 0em;\n color: #888;\n width: 80px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .postfix {\n display: flex;\n align-items: stretch;\n }\n\n .popper {\n padding: 0.5em 0.75em;\n background: rgba(240, 240, 240, 1);\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n --icon-color: #888;\n opacity: 0;\n cursor: default;\n transform: scale(0.5);\n transition: all 300ms ease-in-out;\n display: flex;\n align-items: stretch;\n z-index: 1000;\n }\n\n .postfix temba-icon[name='calendar'] {\n --icon-color: #e3e3e3;\n }\n\n .popper.check {\n background: rgba(90, 145, 86, 0.15);\n }\n\n .popper.none {\n opacity: 0;\n }\n\n .popper.copy temba-icon:hover {\n --icon-color: #555;\n }\n\n .popper.corner-down-left {\n // background: var(--color-primary-dark);\n // --icon-color: var(--color-text-light);\n opacity: 1;\n transform: scale(1);\n }\n\n temba-icon {\n cursor: pointer;\n }\n\n temba-icon[name='check'] {\n --icon-color: rgb(90, 145, 86);\n }\n\n temba-textinput:hover .popper.copy {\n opacity: 1;\n transform: scale(1);\n }\n\n temba-textinput:focus .popper.copy {\n opacity: 1;\n transform: scale(1);\n }\n\n .copy.clicked temba-icon {\n transform: scale(1.2);\n }\n\n temba-icon {\n transition: all 200ms ease-in-out;\n }\n `;\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.handleInput = this.handleInput.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n public handleIconClick(evt: MouseEvent) {\n const ele = evt.target as HTMLDivElement;\n const icon = ele.getAttribute('name');\n const input = this.shadowRoot.querySelector('temba-textinput') as TextInput;\n\n if (icon === 'copy') {\n if (navigator.clipboard) {\n this.iconClass = 'clicked';\n navigator.clipboard.writeText(input.getDisplayValue()).then(() => {\n window.setTimeout(() => {\n this.iconClass = '';\n }, 300);\n });\n }\n }\n evt.preventDefault();\n evt.stopPropagation();\n }\n\n public handleSubmit() {\n const input = this.shadowRoot.querySelector('temba-textinput') as TextInput;\n if (input.value !== this.value) {\n this.value = input.value;\n this.fireEvent('change');\n }\n this.icon = navigator.clipboard ? 'copy' : '';\n }\n\n public handleChange(evt: Event) {\n evt.preventDefault();\n evt.stopPropagation();\n }\n\n public handleInput(evt: KeyboardEvent) {\n if (evt.key === 'Enter') {\n const input = evt.currentTarget as TextInput;\n input.blur();\n }\n }\n\n public render(): TemplateResult {\n return html`\n <div>\n <temba-textinput\n value=\"${this.value ? this.value : ''}\"\n ?datetimepicker=${this.type === 'datetime'}\n @blur=${this.handleSubmit}\n @keydown=${this.handleInput}\n @change=${this.handleChange}\n >\n <div class=\"prefix\" slot=\"prefix\">\n <div class=\"name\">${this.name}</div>\n </div>\n\n <div class=\"postfix\">\n ${this.type === 'datetime'\n ? html`<div\n style=\"position: absolute; padding-top: .75em; padding-left: .75em;\"\n >\n <temba-icon name=\"calendar\" />\n </div>`\n : null}\n\n <div\n class=\"popper ${this.iconClass} ${this.icon ? this.icon : 'none'}\"\n @click=${this.handleIconClick}\n >\n <temba-icon name=\"${this.icon}\" animatechange=\"spin\"></temba-icon>\n </div>\n </div>\n </temba-textinput>\n </div>\n `;\n }\n}\n"]}
@@ -0,0 +1,106 @@
1
+ import { __decorate } from "tslib";
2
+ import { css, html } from 'lit';
3
+ import { property } from 'lit/decorators';
4
+ import { postJSON } from '../utils';
5
+ import { ContactStoreElement } from './ContactStoreElement';
6
+ export class ContactFields extends ContactStoreElement {
7
+ static get styles() {
8
+ return css `
9
+ :host {
10
+ display: flex;
11
+ flex-wrap: wrap;
12
+ flex-shrink: 1;
13
+ }
14
+
15
+ .field {
16
+ display: flex;
17
+ margin: 0.3em 0.3em;
18
+ box-shadow: 0 0 0.2em rgba(0, 0, 0, 0.15);
19
+ border-radius: var(--curvature);
20
+ align-items: center;
21
+ overflow: hidden;
22
+ }
23
+
24
+ .field.set {
25
+ background: #fff;
26
+ }
27
+
28
+ .field.unset {
29
+ opacity: 0.4;
30
+ }
31
+
32
+ .field.unset .label {
33
+ }
34
+
35
+ .field:hover {
36
+ }
37
+
38
+ .field:hover {
39
+ box-shadow: 1px 1px 6px 2px rgba(0, 0, 0, 0.05),
40
+ 0px 0px 0px 2px var(--color-link-primary);
41
+ cursor: pointer;
42
+ }
43
+
44
+ .label {
45
+ padding: 0.25em 1em;
46
+ border-top-left-radius: var(--curvature);
47
+ border-bottom-left-radius: var(--curvature);
48
+ color: #777;
49
+ font-size: 0.9em;
50
+ font-weight: 400;
51
+ box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.1) inset;
52
+ }
53
+
54
+ .value {
55
+ --icon-color: #ddd;
56
+ max-width: 150px;
57
+ white-space: nowrap;
58
+ overflow: hidden;
59
+ text-overflow: ellipsis;
60
+ padding: 0.25em 1em;
61
+ border-top-right-radius: var(--curvature);
62
+ border-bottom-right-radius: var(--curvature);
63
+ font-size: 0.9em;
64
+ }
65
+
66
+ temba-contact-field {
67
+ margin: 0.3em;
68
+ min-width: 320px;
69
+ flex-grow: 1;
70
+ }
71
+ `;
72
+ }
73
+ connectedCallback() {
74
+ super.connectedCallback();
75
+ this.handleFieldChanged = this.handleFieldChanged.bind(this);
76
+ }
77
+ handleFieldChanged(evt) {
78
+ const field = evt.currentTarget;
79
+ this.data.fields[field.key] = field.value;
80
+ postJSON('/api/v2/contacts.json?uuid=' + this.data.uuid, {
81
+ fields: { [field.key]: field.value },
82
+ }).then(() => {
83
+ this.refresh();
84
+ });
85
+ }
86
+ render() {
87
+ const pinned = this.store.getPinnedFields();
88
+ const fields = pinned.map((field) => {
89
+ if (this.data) {
90
+ const value = this.data.fields[field.key];
91
+ return html `<temba-contact-field
92
+ key=${field.key}
93
+ name=${field.label}
94
+ value=${value}
95
+ type=${field.value_type}
96
+ @change=${this.handleFieldChanged}
97
+ ></temba-contact-field>`;
98
+ }
99
+ });
100
+ return html `${this.data ? html ` ${fields} ` : null}`;
101
+ }
102
+ }
103
+ __decorate([
104
+ property({ type: Boolean })
105
+ ], ContactFields.prototype, "dirty", void 0);
106
+ //# sourceMappingURL=ContactFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactFields.js","sourceRoot":"","sources":["../../../src/contacts/ContactFields.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,OAAO,aAAc,SAAQ,mBAAmB;IACpD,MAAM,KAAK,MAAM;QACf,OAAO,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+DT,CAAC;IACJ,CAAC;IAKD,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/D,CAAC;IAEM,kBAAkB,CAAC,GAAe;QACvC,MAAM,KAAK,GAAG,GAAG,CAAC,aAAmC,CAAC;QACtD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;QAC1C,QAAQ,CAAC,6BAA6B,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YACvD,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE;SACrC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;YACX,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,MAAM;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;QAE5C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAmB,EAAE,EAAE;YAChD,IAAI,IAAI,CAAC,IAAI,EAAE;gBACb,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC1C,OAAO,IAAI,CAAA;gBACH,KAAK,CAAC,GAAG;iBACR,KAAK,CAAC,KAAK;kBACV,KAAK;iBACN,KAAK,CAAC,UAAU;oBACb,IAAI,CAAC,kBAAkB;gCACX,CAAC;aAC1B;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,CAAC;CACF;AAnCC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;4CACb","sourcesContent":["import { css, html, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators';\nimport { ContactField } from '../interfaces';\nimport { postJSON } from '../utils';\nimport { ContactFieldEditor } from './ContactFieldEditor';\nimport { ContactStoreElement } from './ContactStoreElement';\n\nexport class ContactFields extends ContactStoreElement {\n static get styles() {\n return css`\n :host {\n display: flex;\n flex-wrap: wrap;\n flex-shrink: 1;\n }\n\n .field {\n display: flex;\n margin: 0.3em 0.3em;\n box-shadow: 0 0 0.2em rgba(0, 0, 0, 0.15);\n border-radius: var(--curvature);\n align-items: center;\n overflow: hidden;\n }\n\n .field.set {\n background: #fff;\n }\n\n .field.unset {\n opacity: 0.4;\n }\n\n .field.unset .label {\n }\n\n .field:hover {\n }\n\n .field:hover {\n box-shadow: 1px 1px 6px 2px rgba(0, 0, 0, 0.05),\n 0px 0px 0px 2px var(--color-link-primary);\n cursor: pointer;\n }\n\n .label {\n padding: 0.25em 1em;\n border-top-left-radius: var(--curvature);\n border-bottom-left-radius: var(--curvature);\n color: #777;\n font-size: 0.9em;\n font-weight: 400;\n box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.1) inset;\n }\n\n .value {\n --icon-color: #ddd;\n max-width: 150px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0.25em 1em;\n border-top-right-radius: var(--curvature);\n border-bottom-right-radius: var(--curvature);\n font-size: 0.9em;\n }\n\n temba-contact-field {\n margin: 0.3em;\n min-width: 320px;\n flex-grow: 1;\n }\n `;\n }\n\n @property({ type: Boolean })\n dirty: boolean;\n\n connectedCallback(): void {\n super.connectedCallback();\n this.handleFieldChanged = this.handleFieldChanged.bind(this);\n }\n\n public handleFieldChanged(evt: InputEvent) {\n const field = evt.currentTarget as ContactFieldEditor;\n this.data.fields[field.key] = field.value;\n postJSON('/api/v2/contacts.json?uuid=' + this.data.uuid, {\n fields: { [field.key]: field.value },\n }).then(() => {\n this.refresh();\n });\n }\n\n public render(): TemplateResult {\n const pinned = this.store.getPinnedFields();\n\n const fields = pinned.map((field: ContactField) => {\n if (this.data) {\n const value = this.data.fields[field.key];\n return html`<temba-contact-field\n key=${field.key}\n name=${field.label}\n value=${value}\n type=${field.value_type}\n @change=${this.handleFieldChanged}\n ></temba-contact-field>`;\n }\n });\n\n return html`${this.data ? html` ${fields} ` : null}`;\n }\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { css, html } from 'lit';
2
+ import { ContactStoreElement } from './ContactStoreElement';
3
+ export class ContactGroups extends ContactStoreElement {
4
+ static get styles() {
5
+ return css `
6
+ .groups {
7
+ display: flex;
8
+ flex-wrap: wrap;
9
+ }
10
+
11
+ temba-label {
12
+ margin: 0.3em;
13
+ }
14
+ `;
15
+ }
16
+ render() {
17
+ return html `${this.data
18
+ ? html `
19
+ <div class="groups">
20
+ ${this.data.groups.map((group) => {
21
+ return html `
22
+ <temba-label
23
+ onclick="goto(event)"
24
+ href="/contact/filter/${group.uuid}/"
25
+ icon=${group.is_dynamic ? 'atom' : 'users'}
26
+ clickable
27
+ light
28
+ shadow
29
+ >
30
+ ${group.name}
31
+ </temba-label>
32
+ `;
33
+ })}
34
+ </div>
35
+ `
36
+ : null}`;
37
+ }
38
+ }
39
+ //# sourceMappingURL=ContactGroups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactGroups.js","sourceRoot":"","sources":["../../../src/contacts/ContactGroups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,OAAO,aAAc,SAAQ,mBAAmB;IACpD,MAAM,KAAK,MAAM;QACf,OAAO,GAAG,CAAA;;;;;;;;;KAST,CAAC;IACJ,CAAC;IAEM,MAAM;QACX,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,IAAI;YACrB,CAAC,CAAC,IAAI,CAAA;;cAEE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAY,EAAE,EAAE;gBACtC,OAAO,IAAI,CAAA;;;0CAGiB,KAAK,CAAC,IAAI;yBAC3B,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;;;;oBAKxC,KAAK,CAAC,IAAI;;eAEf,CAAC;YACJ,CAAC,CAAC;;SAEL;YACH,CAAC,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;CACF","sourcesContent":["import { css, html, TemplateResult } from 'lit';\nimport { Group } from '../interfaces';\nimport { ContactStoreElement } from './ContactStoreElement';\n\nexport class ContactGroups extends ContactStoreElement {\n static get styles() {\n return css`\n .groups {\n display: flex;\n flex-wrap: wrap;\n }\n\n temba-label {\n margin: 0.3em;\n }\n `;\n }\n\n public render(): TemplateResult {\n return html`${this.data\n ? html`\n <div class=\"groups\">\n ${this.data.groups.map((group: Group) => {\n return html`\n <temba-label\n onclick=\"goto(event)\"\n href=\"/contact/filter/${group.uuid}/\"\n icon=${group.is_dynamic ? 'atom' : 'users'}\n clickable\n light\n shadow\n >\n ${group.name}\n </temba-label>\n `;\n })}\n </div>\n `\n : null}`;\n }\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { __decorate } from "tslib";
2
+ import { css, html } from 'lit';
3
+ import { property } from 'lit/decorators';
4
+ import { ContactStoreElement } from './ContactStoreElement';
5
+ export class ContactName extends ContactStoreElement {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.size = 20;
9
+ }
10
+ static get styles() {
11
+ return css `
12
+ :host {
13
+ display: flex;
14
+ }
15
+
16
+ temba-urn {
17
+ margin-right: 0.2em;
18
+ margin-top: 2px;
19
+ }
20
+ `;
21
+ }
22
+ render() {
23
+ if (this.data) {
24
+ const urn = this.data.urns.length > 0
25
+ ? html `<temba-urn
26
+ size=${this.size}
27
+ urn="${this.data.urns[0]}"
28
+ ></temba-urn>`
29
+ : null;
30
+ return html `
31
+ ${urn}
32
+ <div class="name">${this.data.name}</div>
33
+ `;
34
+ }
35
+ }
36
+ }
37
+ __decorate([
38
+ property({ type: Number, attribute: 'icon-size' })
39
+ ], ContactName.prototype, "size", void 0);
40
+ //# sourceMappingURL=ContactName.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactName.js","sourceRoot":"","sources":["../../../src/contacts/ContactName.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,OAAO,WAAY,SAAQ,mBAAmB;IAApD;;QAEE,SAAI,GAAG,EAAE,CAAC;IA8BZ,CAAC;IA5BC,MAAM,KAAK,MAAM;QACf,OAAO,GAAG,CAAA;;;;;;;;;KAST,CAAC;IACJ,CAAC;IAEM,MAAM;QACX,IAAI,IAAI,CAAC,IAAI,EAAE;YACb,MAAM,GAAG,GACP,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;gBACvB,CAAC,CAAC,IAAI,CAAA;qBACK,IAAI,CAAC,IAAI;qBACT,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;0BACZ;gBAChB,CAAC,CAAC,IAAI,CAAC;YACX,OAAO,IAAI,CAAA;UACP,GAAG;4BACe,IAAI,CAAC,IAAI,CAAC,IAAI;OACnC,CAAC;SACH;IACH,CAAC;CACF;AA9BC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;yCACzC","sourcesContent":["import { css, html, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators';\nimport { ContactStoreElement } from './ContactStoreElement';\n\nexport class ContactName extends ContactStoreElement {\n @property({ type: Number, attribute: 'icon-size' })\n size = 20;\n\n static get styles() {\n return css`\n :host {\n display: flex;\n }\n\n temba-urn {\n margin-right: 0.2em;\n margin-top: 2px;\n }\n `;\n }\n\n public render(): TemplateResult {\n if (this.data) {\n const urn =\n this.data.urns.length > 0\n ? html`<temba-urn\n size=${this.size}\n urn=\"${this.data.urns[0]}\"\n ></temba-urn>`\n : null;\n return html`\n ${urn}\n <div class=\"name\">${this.data.name}</div>\n `;\n }\n }\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import { __decorate } from "tslib";
2
+ import { property } from 'lit/decorators';
3
+ import { StoreElement } from '../store/StoreElement';
4
+ export class ContactStoreElement extends StoreElement {
5
+ prepareData(data) {
6
+ if (data && data.length > 0) {
7
+ data = data[0];
8
+ data.groups.forEach((group) => {
9
+ group.is_dynamic = this.store.isDynamicGroup(group.uuid);
10
+ });
11
+ data.groups.sort((a, b) => {
12
+ if (!a.is_dynamic || !b.is_dynamic) {
13
+ if (a.is_dynamic) {
14
+ return -1;
15
+ }
16
+ if (b.is_dynamic) {
17
+ return 1;
18
+ }
19
+ }
20
+ return a.name.localeCompare(b.name);
21
+ });
22
+ return data;
23
+ }
24
+ return null;
25
+ }
26
+ updated(changes) {
27
+ super.updated(changes);
28
+ if (changes.has('contact')) {
29
+ if (this.contact) {
30
+ this.url = `/api/v2/contacts.json?uuid=${this.contact}`;
31
+ }
32
+ else {
33
+ this.url = null;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ __decorate([
39
+ property({ type: String })
40
+ ], ContactStoreElement.prototype, "contact", void 0);
41
+ __decorate([
42
+ property({ type: Object, attribute: false })
43
+ ], ContactStoreElement.prototype, "data", void 0);
44
+ //# sourceMappingURL=ContactStoreElement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactStoreElement.js","sourceRoot":"","sources":["../../../src/contacts/ContactStoreElement.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,OAAO,mBAAoB,SAAQ,YAAY;IAOnD,WAAW,CAAC,IAAS;QACnB,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;YAC3B,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAY,EAAE,EAAE;gBACnC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3D,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAQ,EAAE,CAAQ,EAAE,EAAE;gBACtC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE;oBAClC,IAAI,CAAC,CAAC,UAAU,EAAE;wBAChB,OAAO,CAAC,CAAC,CAAC;qBACX;oBAED,IAAI,CAAC,CAAC,UAAU,EAAE;wBAChB,OAAO,CAAC,CAAC;qBACV;iBACF;gBAED,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YAEH,OAAO,IAAI,CAAC;SACb;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAES,OAAO,CACf,OAA0D;QAE1D,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACvB,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;YAC1B,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,IAAI,CAAC,GAAG,GAAG,8BAA8B,IAAI,CAAC,OAAO,EAAE,CAAC;aACzD;iBAAM;gBACL,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;aACjB;SACF;IACH,CAAC;CACF;AA3CC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;oDACX;AAGhB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;iDAC/B","sourcesContent":["import { PropertyValueMap } from 'lit';\nimport { property } from 'lit/decorators';\nimport { Contact, Group } from '../interfaces';\nimport { StoreElement } from '../store/StoreElement';\n\nexport class ContactStoreElement extends StoreElement {\n @property({ type: String })\n contact: string;\n\n @property({ type: Object, attribute: false })\n data: Contact;\n\n prepareData(data: any) {\n if (data && data.length > 0) {\n data = data[0];\n data.groups.forEach((group: Group) => {\n group.is_dynamic = this.store.isDynamicGroup(group.uuid);\n });\n\n data.groups.sort((a: Group, b: Group) => {\n if (!a.is_dynamic || !b.is_dynamic) {\n if (a.is_dynamic) {\n return -1;\n }\n\n if (b.is_dynamic) {\n return 1;\n }\n }\n\n return a.name.localeCompare(b.name);\n });\n\n return data;\n }\n return null;\n }\n\n protected updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n if (changes.has('contact')) {\n if (this.contact) {\n this.url = `/api/v2/contacts.json?uuid=${this.contact}`;\n } else {\n this.url = null;\n }\n }\n }\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import { __decorate } from "tslib";
2
+ import { css, html } from 'lit';
3
+ import { property } from 'lit/decorators';
4
+ import { RapidElement } from '../RapidElement';
5
+ export class ContactUrn extends RapidElement {
6
+ static get styles() {
7
+ return css `
8
+ .urn {
9
+ box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.04) inset;
10
+ padding: 3px;
11
+ border: 1px solid #ddd;
12
+ border-radius: 18rem;
13
+ background: #eee;
14
+ margin-right: 0.2em;
15
+ }
16
+ `;
17
+ }
18
+ render() {
19
+ const scheme = this.urn.split(':')[0];
20
+ return html `
21
+ <img
22
+ class="urn"
23
+ width="${this.size}em"
24
+ height="${this.size}em"
25
+ src="${this.prefix ||
26
+ window.static_url ||
27
+ '/static/'}img/schemes/${scheme}.svg"
28
+ />
29
+ `;
30
+ }
31
+ }
32
+ __decorate([
33
+ property({ type: String })
34
+ ], ContactUrn.prototype, "urn", void 0);
35
+ __decorate([
36
+ property({ type: Number })
37
+ ], ContactUrn.prototype, "size", void 0);
38
+ //# sourceMappingURL=ContactUrn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactUrn.js","sourceRoot":"","sources":["../../../src/contacts/ContactUrn.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,OAAO,UAAW,SAAQ,YAAY;IAO1C,MAAM,KAAK,MAAM;QACf,OAAO,GAAG,CAAA;;;;;;;;;KAST,CAAC;IACJ,CAAC;IAEM,MAAM;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,OAAO,IAAI,CAAA;;;iBAGE,IAAI,CAAC,IAAI;kBACR,IAAI,CAAC,IAAI;eACZ,IAAI,CAAC,MAAM;YACjB,MAAc,CAAC,UAAU;YAC1B,UAAU,eAAe,MAAM;;KAElC,CAAC;IACJ,CAAC;CACF;AA/BC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;uCACf;AAGZ;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;wCACd","sourcesContent":["import { css, html, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators';\nimport { RapidElement } from '../RapidElement';\n\nexport class ContactUrn extends RapidElement {\n @property({ type: String })\n urn: string;\n\n @property({ type: Number })\n size: number;\n\n static get styles() {\n return css`\n .urn {\n box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.04) inset;\n padding: 3px;\n border: 1px solid #ddd;\n border-radius: 18rem;\n background: #eee;\n margin-right: 0.2em;\n }\n `;\n }\n\n public render(): TemplateResult {\n const scheme = this.urn.split(':')[0];\n return html`\n <img\n class=\"urn\"\n width=\"${this.size}em\"\n height=\"${this.size}em\"\n src=\"${this.prefix ||\n (window as any).static_url ||\n '/static/'}img/schemes/${scheme}.svg\"\n />\n `;\n }\n}\n"]}
@@ -434,6 +434,10 @@ export const getEventStyles = () => {
434
434
  .assigned .attn {
435
435
  color: #777;
436
436
  }
437
+
438
+ .attachments {
439
+ margin-top: 1em;
440
+ }
437
441
  `;
438
442
  };
439
443
  const FLOW_USER_ID = 'flow';
@@ -538,14 +542,48 @@ export const renderAttachment = (attachment) => {
538
542
  }
539
543
  else if (ext === 'pdf') {
540
544
  return html `<div
541
- style="width:100%;height:300px;border-radius:var(--curvature);box-shadow:0px 0px 10px -1px rgb(160 160 160);overflow:hidden"
542
- ><embed src="${url}#view=Fit" type="application/pdf" frameBorder="0" scrolling="auto" height="100%" width="100%"></embed></div>`;
545
+ style="width:100%;height:300px;border-radius:var(--curvature);box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);overflow:hidden"
546
+ ><embed src="${url}#view=Fit" type="application/pdf" frameBorder="0" scrolling="auto" height="100%" width="100%"></embed></div>`;
543
547
  }
544
548
  else if (mediaType === 'video') {
545
- return html `<video max-width="400px" height="auto" controls="controls">
549
+ return html `<video
550
+ style="border-radius:var(--curvature);box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);"
551
+ max-width="400px"
552
+ height="auto"
553
+ controls="controls"
554
+ >
546
555
  <source src="${url}" type="video/mp4" />
547
556
  </video> `;
548
557
  }
558
+ else if (mediaType === 'audio') {
559
+ return html `<audio
560
+ style="border-radius: 99px; box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);"
561
+ src="${url}"
562
+ type="${attType}"
563
+ controls
564
+ >
565
+ <a target="_" href="${url}">${url}</a>
566
+ </audio>`;
567
+ }
568
+ else if (attType === 'geo') {
569
+ const [lat, long] = url.split(',');
570
+ const latFloat = parseFloat(lat);
571
+ const longFloat = parseFloat(long);
572
+ const geo = `${lat}000000%2C${long}000000`;
573
+ return html ` <iframe
574
+ style="border-radius: var(--curvature);box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);"
575
+ width="300"
576
+ height="300"
577
+ frameborder="0"
578
+ scrolling="no"
579
+ marginheight="0"
580
+ marginwidth="0"
581
+ src="https://www.openstreetmap.org/export/embed.html?bbox=${longFloat -
582
+ 0.005}000000%2C${latFloat - 0.005}%2C${longFloat +
583
+ 0.005}000000%2C${latFloat +
584
+ 0.005}000000&amp;layer=mapnik&amp;marker=${geo}"
585
+ ></iframe>`;
586
+ }
549
587
  else {
550
588
  return html `<div style="display:flex">
551
589
  <temba-icon name="download"></temba-icon>
@@ -553,15 +591,46 @@ export const renderAttachment = (attachment) => {
553
591
  </div>`;
554
592
  }
555
593
  return html `<div
556
- style="width:100%;max-width:300px;border-radius:var(--curvature); box-shadow:0px 0px 10px -1px rgb(160 160 160);overflow:hidden"
594
+ style="width:100%;max-width:300px;border-radius:var(--curvature); box-shadow:0px 0px 6px 0px rgba(0,0,0,.15);overflow:hidden"
557
595
  >
558
596
  ${inner}
559
597
  </div>`;
560
598
  };
561
599
  export const renderMsgEvent = (event, agent) => {
562
600
  const isInbound = event.type === Events.MESSAGE_RECEIVED;
563
- const isError = event.status === 'E' || event.status === 'F';
564
- const msg = html `<div style="display:flex;align-items:flex-start">
601
+ const isError = event.status === 'E';
602
+ const isFailure = event.status === 'F';
603
+ // summary items which appear under the message bubble
604
+ const summary = [];
605
+ if (event.logs_url) {
606
+ summary.push(html ` <div class="icon-link">
607
+ <temba-icon
608
+ onclick="goto(event)"
609
+ href="${event.logs_url}"
610
+ name="log"
611
+ class="${isError || isFailure ? 'error' : ''}"
612
+ ></temba-icon>
613
+ </div>`);
614
+ }
615
+ else if (isError) {
616
+ summary.push(html `<temba-icon
617
+ title="Message delivery error"
618
+ name="alert-triangle"
619
+ ></temba-icon>`);
620
+ }
621
+ else if (isFailure) {
622
+ summary.push(html `<temba-icon
623
+ title="Message delivery failure: ${event.failed_reason_display}"
624
+ name="alert-triangle"
625
+ ></temba-icon>`);
626
+ }
627
+ if (event.recipient_count > 1) {
628
+ summary.push(html `<temba-icon size="1" name="megaphone"></temba-icon>
629
+ <div class="recipients">${event.recipient_count} contacts</div>
630
+ <div class="separator">•</div>`);
631
+ }
632
+ summary.push(html `<div class="time">${timeSince(new Date(event.created_on))}</div>`);
633
+ return html `<div style="display:flex;align-items:flex-start">
565
634
  <div style="display:flex;flex-direction:column">
566
635
  ${event.msg.text ? html `<div class="msg">${event.msg.text}</div>` : null}
567
636
  ${event.msg.attachments
@@ -577,29 +646,7 @@ export const renderMsgEvent = (event, agent) => {
577
646
  style="flex-direction:row${isInbound ? '-reverse' : ''}"
578
647
  >
579
648
  <div style="flex-grow:1"></div>
580
- ${event.logs_url
581
- ? html `
582
- <div class="icon-link">
583
- <temba-icon
584
- onclick="goto(event)"
585
- href="${event.logs_url}"
586
- name="log"
587
- class="${isError ? 'error' : ''}"
588
- ></temba-icon>
589
- </div>
590
- `
591
- : isError
592
- ? html `<temba-icon
593
- title="Message delivery error"
594
- name="alert-triangle"
595
- ></temba-icon>`
596
- : null}
597
- ${event.recipient_count > 1
598
- ? html `<temba-icon size="1" name="megaphone"></temba-icon>
599
- <div class="recipients">${event.recipient_count} contacts</div>
600
- <div class="separator">•</div>`
601
- : null}
602
- <div class="time">${timeSince(new Date(event.created_on))}</div>
649
+ ${summary}
603
650
  </div>
604
651
  </div>
605
652
 
@@ -613,7 +660,6 @@ export const renderMsgEvent = (event, agent) => {
613
660
  </div>`
614
661
  : null}
615
662
  </div>`;
616
- return msg;
617
663
  };
618
664
  export const renderFlowEvent = (event) => {
619
665
  let verb = 'Interrupted';