@nyaruka/temba-components 0.35.0 → 0.35.2

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 (51) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{30636513.js → 41042aab.js} +89 -64
  3. package/dist/index.js +89 -64
  4. package/dist/sw.js +1 -1
  5. package/dist/sw.js.map +1 -1
  6. package/dist/templates/components-body.html +1 -1
  7. package/dist/templates/components-head.html +1 -1
  8. package/out-tsc/src/contacts/ContactPending.js +30 -35
  9. package/out-tsc/src/contacts/ContactPending.js.map +1 -1
  10. package/out-tsc/src/contacts/events.js +24 -10
  11. package/out-tsc/src/contacts/events.js.map +1 -1
  12. package/out-tsc/src/date/TembaDate.js +37 -11
  13. package/out-tsc/src/date/TembaDate.js.map +1 -1
  14. package/out-tsc/src/dropdown/Dropdown.js +30 -5
  15. package/out-tsc/src/dropdown/Dropdown.js.map +1 -1
  16. package/out-tsc/src/list/RunList.js +17 -16
  17. package/out-tsc/src/list/RunList.js.map +1 -1
  18. package/out-tsc/src/store/Store.js +12 -26
  19. package/out-tsc/src/store/Store.js.map +1 -1
  20. package/out-tsc/src/vectoricon/index.js +3 -0
  21. package/out-tsc/src/vectoricon/index.js.map +1 -1
  22. package/out-tsc/test/temba-contact-history.test.js +5 -8
  23. package/out-tsc/test/temba-contact-history.test.js.map +1 -1
  24. package/out-tsc/test/temba-date.test.js +3 -9
  25. package/out-tsc/test/temba-date.test.js.map +1 -1
  26. package/out-tsc/test/utils.test.js +9 -0
  27. package/out-tsc/test/utils.test.js.map +1 -1
  28. package/package.json +1 -1
  29. package/screenshots/truth/contacts/contact-active-default.png +0 -0
  30. package/screenshots/truth/contacts/contact-active-show-chat-history.png +0 -0
  31. package/screenshots/truth/contacts/contact-active-show-chat-msg.png +0 -0
  32. package/screenshots/truth/contacts/contact-archived-hide-chat-msg.png +0 -0
  33. package/screenshots/truth/contacts/contact-archived-show-chat-history.png +0 -0
  34. package/screenshots/truth/contacts/contact-blocked-hide-chat-msg.png +0 -0
  35. package/screenshots/truth/contacts/contact-blocked-show-chat-history.png +0 -0
  36. package/screenshots/truth/contacts/contact-stopped-hide-chat-msg.png +0 -0
  37. package/screenshots/truth/contacts/contact-stopped-show-chat-history.png +0 -0
  38. package/screenshots/truth/contacts/fields-updated.png +0 -0
  39. package/screenshots/truth/contacts/history-expanded.png +0 -0
  40. package/screenshots/truth/contacts/history.png +0 -0
  41. package/screenshots/truth/date/duration.png +0 -0
  42. package/src/contacts/ContactPending.ts +38 -34
  43. package/src/contacts/events.ts +24 -10
  44. package/src/date/TembaDate.ts +40 -11
  45. package/src/dropdown/Dropdown.ts +29 -5
  46. package/src/list/RunList.ts +17 -28
  47. package/src/store/Store.ts +13 -37
  48. package/src/vectoricon/index.ts +3 -0
  49. package/test/temba-contact-history.test.ts +7 -7
  50. package/test/temba-date.test.ts +3 -8
  51. package/test/utils.test.ts +10 -1
@@ -1,4 +1,6 @@
1
1
  import '../temba-modules';
2
+ import { DateTime } from 'luxon';
3
+ import * as sinon from 'sinon';
2
4
  import { stub } from 'sinon';
3
5
  import { expect, fixture, html, assert } from '@open-wc/testing';
4
6
  import MouseHelper from './MouseHelper';
@@ -160,4 +162,11 @@ export const loadStore = async () => {
160
162
  await store.httpComplete;
161
163
  return store;
162
164
  };
165
+ export const mockNow = (isodate) => {
166
+ const now = DateTime.fromISO(isodate);
167
+ // mock the current time
168
+ sinon.replace(DateTime, 'now', () => {
169
+ return now;
170
+ });
171
+ };
163
172
  //# sourceMappingURL=utils.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.test.js","sourceRoot":"","sources":["../../test/utils.test.ts"],"names":[],"mappings":"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","sourcesContent":["import '../temba-modules';\n\ninterface Clip {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nimport { stub } from 'sinon';\nimport { expect, fixture, html, assert } from '@open-wc/testing';\nimport MouseHelper from './MouseHelper';\nimport { Store } from '../src/store/Store';\n\nexport interface CodeMock {\n endpoint: RegExp;\n body: string;\n headers: any;\n}\n\nconst gets: CodeMock[] = [];\nconst posts: CodeMock[] = [];\nlet normalFetch;\n\nexport const showMouse = async () => {\n const mouse = await fixture(html`<mouse-helper />`);\n assert.instanceOf(mouse, MouseHelper);\n};\n\nexport const getAttributes = (attrs: any = {}) => {\n return `${Object.keys(attrs)\n .map((name: string) => {\n if (typeof attrs[name] === 'boolean' && attrs[name]) {\n return name;\n }\n return `${name}='${attrs[name]}'`;\n })\n .join(' ')}`;\n};\n\nexport const getComponent = async (\n tag,\n attrs: any = {},\n slot = '',\n width = 250,\n height = 0,\n style = ''\n) => {\n const spec = `<${tag} ${getAttributes(attrs)}>${slot}</${tag}>`;\n const parentNode = document.createElement('div');\n const styleAttribute = `\n ${width > 0 ? `width:${width}px;` : ``} \n ${height > 0 ? `height:${height}px;` : ``}\n ${style ? style : ``}\n `;\n\n parentNode.setAttribute('style', styleAttribute);\n return await fixture(spec, { parentNode });\n};\n\nconst createResponse = mocked => {\n const mockResponse = new window.Response(mocked.body, {\n status: 200,\n headers: {\n 'Content-type': 'text/html',\n ...mocked.headers,\n },\n });\n\n return Promise.resolve(mockResponse);\n};\n\nconst createJSONResponse = mocked => {\n const mockResponse = new window.Response(JSON.stringify(mocked.body), {\n status: 200,\n headers: {\n 'Content-type': 'application/json',\n ...mocked.headers,\n },\n });\n\n return Promise.resolve(mockResponse);\n};\n\nconst getResponse = (endpoint: string, options) => {\n // check if our path has been mocked in code\n const mocks = options.method === 'GET' ? gets : posts;\n const codeMock = mocks.find(mock => mock.endpoint.test(endpoint));\n\n if (codeMock) {\n if (typeof codeMock.body === 'string') {\n // see if we are being mocked to a file\n if (codeMock.body.startsWith('/')) {\n endpoint = codeMock.body;\n } else {\n return createResponse(codeMock);\n }\n } else {\n return createJSONResponse(codeMock);\n }\n }\n\n // otherwise fetch over http\n return normalFetch(endpoint, options);\n};\n\nbefore(async () => {\n normalFetch = window.fetch;\n stub(window, 'fetch').callsFake(getResponse);\n await setViewport({ width: 1024, height: 768, deviceScaleFactor: 2 });\n});\n\nafter(() => {\n (window.fetch as any).restore();\n});\n\nexport const mockGET = (endpoint: RegExp, body: any, headers: any = {}) => {\n gets.push({ endpoint, body, headers });\n};\n\nexport const mockPOST = (endpoint: RegExp, body: any, headers: any = {}) => {\n posts.push({ endpoint, body, headers });\n};\n\nexport const checkTimers = (clock: any) => {\n expect(\n Object.keys(clock.timers).length,\n `Timers still to be run ${JSON.stringify(clock.timers)}`\n ).to.equal(0);\n};\n\nexport const delay = (millis: number) => {\n return new Promise(function (resolve) {\n setTimeout(resolve, millis);\n });\n};\n\nexport const assertScreenshot = async (\n filename: string,\n clip: Clip,\n threshold = 0.1,\n exclude: Clip[] = []\n) => {\n // const screenShotsEnabled = !!__karma__.config.args.find(\n // (option: string) => option === '--screenshots'\n // );\n\n await (window as any).waitFor(200);\n\n // console.log((window as any).watched);\n if ((window as any).watched) {\n // return;\n }\n\n const mochaUI = document.querySelector('#mocha');\n mochaUI.classList.add('screenshots');\n\n try {\n await (window as any).matchPageSnapshot(\n `${filename}.png`,\n clip,\n exclude,\n threshold\n );\n } catch (error) {\n if (error.message) {\n throw new Error(\n `${error.message} ${\n error.expected\n ? `Expected ${error.expected} but got ${error.actual}`\n : ''\n } ${error.files ? `\\n${error.files.join('\\n')}` : ''}`\n );\n }\n throw new Error(error);\n } finally {\n mochaUI.classList.remove('screenshots');\n }\n};\n\nexport const getClip = (ele: HTMLElement) => {\n let clip: any = ele.getBoundingClientRect();\n if (!clip.width || !clip.height) {\n clip = ele.shadowRoot.firstElementChild.getBoundingClientRect();\n }\n\n const padding = 10;\n const width = clip.width + padding * 2;\n const height = clip.height + padding * 2;\n const y = clip.y - padding;\n const x = clip.x - padding;\n\n const newClip = {\n x,\n y,\n width,\n height,\n bottom: y + height,\n right: x + width,\n top: y,\n left: x,\n };\n\n return newClip;\n};\n\nexport const getHTMLAttrs = (attrs: any = {}) => {\n return Object.keys(attrs)\n .map((name: string) => `${name}='${attrs[name]}'`)\n .join(' ');\n};\n\nexport const getHTML = (tag: string, attrs: any = {}) => {\n return `<${tag} ${getHTMLAttrs(attrs)}></${tag}>`;\n};\n\nexport const loadStore = async () => {\n const store: Store = await fixture(\n `<temba-store \n completion='/test-assets/store/editor.json'\n groups='/test-assets/store/groups.json'\n languages='/test-assets/store/languages.json'\n fields='/test-assets/store/fields.json'\n />`\n );\n await store.httpComplete;\n await store.httpComplete;\n\n return store;\n};\n"]}
1
+ {"version":3,"file":"utils.test.js","sourceRoot":"","sources":["../../test/utils.test.ts"],"names":[],"mappings":"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","sourcesContent":["import '../temba-modules';\nimport { DateTime } from 'luxon';\nimport * as sinon from 'sinon';\ninterface Clip {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nimport { stub } from 'sinon';\nimport { expect, fixture, html, assert } from '@open-wc/testing';\nimport MouseHelper from './MouseHelper';\nimport { Store } from '../src/store/Store';\n\nexport interface CodeMock {\n endpoint: RegExp;\n body: string;\n headers: any;\n}\n\nconst gets: CodeMock[] = [];\nconst posts: CodeMock[] = [];\nlet normalFetch;\n\nexport const showMouse = async () => {\n const mouse = await fixture(html`<mouse-helper />`);\n assert.instanceOf(mouse, MouseHelper);\n};\n\nexport const getAttributes = (attrs: any = {}) => {\n return `${Object.keys(attrs)\n .map((name: string) => {\n if (typeof attrs[name] === 'boolean' && attrs[name]) {\n return name;\n }\n return `${name}='${attrs[name]}'`;\n })\n .join(' ')}`;\n};\n\nexport const getComponent = async (\n tag,\n attrs: any = {},\n slot = '',\n width = 250,\n height = 0,\n style = ''\n) => {\n const spec = `<${tag} ${getAttributes(attrs)}>${slot}</${tag}>`;\n const parentNode = document.createElement('div');\n const styleAttribute = `\n ${width > 0 ? `width:${width}px;` : ``} \n ${height > 0 ? `height:${height}px;` : ``}\n ${style ? style : ``}\n `;\n\n parentNode.setAttribute('style', styleAttribute);\n return await fixture(spec, { parentNode });\n};\n\nconst createResponse = mocked => {\n const mockResponse = new window.Response(mocked.body, {\n status: 200,\n headers: {\n 'Content-type': 'text/html',\n ...mocked.headers,\n },\n });\n\n return Promise.resolve(mockResponse);\n};\n\nconst createJSONResponse = mocked => {\n const mockResponse = new window.Response(JSON.stringify(mocked.body), {\n status: 200,\n headers: {\n 'Content-type': 'application/json',\n ...mocked.headers,\n },\n });\n\n return Promise.resolve(mockResponse);\n};\n\nconst getResponse = (endpoint: string, options) => {\n // check if our path has been mocked in code\n const mocks = options.method === 'GET' ? gets : posts;\n const codeMock = mocks.find(mock => mock.endpoint.test(endpoint));\n\n if (codeMock) {\n if (typeof codeMock.body === 'string') {\n // see if we are being mocked to a file\n if (codeMock.body.startsWith('/')) {\n endpoint = codeMock.body;\n } else {\n return createResponse(codeMock);\n }\n } else {\n return createJSONResponse(codeMock);\n }\n }\n\n // otherwise fetch over http\n return normalFetch(endpoint, options);\n};\n\nbefore(async () => {\n normalFetch = window.fetch;\n stub(window, 'fetch').callsFake(getResponse);\n await setViewport({ width: 1024, height: 768, deviceScaleFactor: 2 });\n});\n\nafter(() => {\n (window.fetch as any).restore();\n});\n\nexport const mockGET = (endpoint: RegExp, body: any, headers: any = {}) => {\n gets.push({ endpoint, body, headers });\n};\n\nexport const mockPOST = (endpoint: RegExp, body: any, headers: any = {}) => {\n posts.push({ endpoint, body, headers });\n};\n\nexport const checkTimers = (clock: any) => {\n expect(\n Object.keys(clock.timers).length,\n `Timers still to be run ${JSON.stringify(clock.timers)}`\n ).to.equal(0);\n};\n\nexport const delay = (millis: number) => {\n return new Promise(function (resolve) {\n setTimeout(resolve, millis);\n });\n};\n\nexport const assertScreenshot = async (\n filename: string,\n clip: Clip,\n threshold = 0.1,\n exclude: Clip[] = []\n) => {\n // const screenShotsEnabled = !!__karma__.config.args.find(\n // (option: string) => option === '--screenshots'\n // );\n\n await (window as any).waitFor(200);\n\n // console.log((window as any).watched);\n if ((window as any).watched) {\n // return;\n }\n\n const mochaUI = document.querySelector('#mocha');\n mochaUI.classList.add('screenshots');\n\n try {\n await (window as any).matchPageSnapshot(\n `${filename}.png`,\n clip,\n exclude,\n threshold\n );\n } catch (error) {\n if (error.message) {\n throw new Error(\n `${error.message} ${\n error.expected\n ? `Expected ${error.expected} but got ${error.actual}`\n : ''\n } ${error.files ? `\\n${error.files.join('\\n')}` : ''}`\n );\n }\n throw new Error(error);\n } finally {\n mochaUI.classList.remove('screenshots');\n }\n};\n\nexport const getClip = (ele: HTMLElement) => {\n let clip: any = ele.getBoundingClientRect();\n if (!clip.width || !clip.height) {\n clip = ele.shadowRoot.firstElementChild.getBoundingClientRect();\n }\n\n const padding = 10;\n const width = clip.width + padding * 2;\n const height = clip.height + padding * 2;\n const y = clip.y - padding;\n const x = clip.x - padding;\n\n const newClip = {\n x,\n y,\n width,\n height,\n bottom: y + height,\n right: x + width,\n top: y,\n left: x,\n };\n\n return newClip;\n};\n\nexport const getHTMLAttrs = (attrs: any = {}) => {\n return Object.keys(attrs)\n .map((name: string) => `${name}='${attrs[name]}'`)\n .join(' ');\n};\n\nexport const getHTML = (tag: string, attrs: any = {}) => {\n return `<${tag} ${getHTMLAttrs(attrs)}></${tag}>`;\n};\n\nexport const loadStore = async () => {\n const store: Store = await fixture(\n `<temba-store \n completion='/test-assets/store/editor.json'\n groups='/test-assets/store/groups.json'\n languages='/test-assets/store/languages.json'\n fields='/test-assets/store/fields.json'\n />`\n );\n await store.httpComplete;\n await store.httpComplete;\n\n return store;\n};\n\nexport const mockNow = (isodate: string) => {\n const now = DateTime.fromISO(isodate);\n // mock the current time\n sinon.replace(DateTime, 'now', () => {\n return now;\n });\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
Binary file
@@ -1,6 +1,10 @@
1
1
  import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators';
3
- import { ScheduledEvent, ScheduledEventType } from '../interfaces';
3
+ import {
4
+ CustomEventType,
5
+ ScheduledEvent,
6
+ ScheduledEventType,
7
+ } from '../interfaces';
4
8
  import { StoreElement } from '../store/StoreElement';
5
9
  import { Icon } from '../vectoricon';
6
10
 
@@ -92,6 +96,12 @@ export class ContactPending extends StoreElement {
92
96
  0 0 0px 1px rgba(0, 0, 0, 0.02);
93
97
  }
94
98
 
99
+ .event:hover {
100
+ cursor: pointer;
101
+ box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.055),
102
+ 0 0 0px 2px var(--color-link-primary);
103
+ }
104
+
95
105
  .time {
96
106
  white-space: nowrap;
97
107
  background: rgba(0, 0, 0, 0.02);
@@ -128,18 +138,17 @@ export class ContactPending extends StoreElement {
128
138
  margin-right: 0.25em;
129
139
  }
130
140
 
131
- .campaign_event .scheduled-by:hover {
132
- color: var(--color-link-primary);
133
- --icon-color: var(--color-link-primary);
134
- cursor: pointer;
135
- }
136
-
137
141
  .scheduled-by .name {
138
142
  flex-grow: 1;
139
143
  }
140
144
  `;
141
145
  }
142
146
 
147
+ constructor() {
148
+ super();
149
+ this.handleEventClicked = this.handleEventClicked.bind(this);
150
+ }
151
+
143
152
  protected updated(
144
153
  changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
145
154
  ): void {
@@ -153,52 +162,47 @@ export class ContactPending extends StoreElement {
153
162
  }
154
163
  }
155
164
 
156
- public renderEvent(event: ScheduledEvent) {
165
+ public handleEventClicked(event: ScheduledEvent) {
166
+ this.fireCustomEvent(CustomEventType.Selection, event);
167
+ }
168
+
169
+ public renderEvent(scheduledEvent: ScheduledEvent) {
157
170
  return html`
158
- <div class="event ${event.type}">
171
+ <div
172
+ class="event ${scheduledEvent.type}"
173
+ @click="${() => this.handleEventClicked(scheduledEvent)}"
174
+ >
159
175
  <div class="type">
160
176
  <temba-icon
161
177
  size="2"
162
- name="${event.message ? Icon.message : Icon.flow}"
178
+ name="${scheduledEvent.message ? Icon.message : Icon.flow}"
163
179
  ></temba-icon>
164
180
  </div>
165
181
 
166
182
  <div class="details">
167
183
  <div>
168
- ${event.flow
169
- ? html`
170
- <div
171
- class="flow linked"
172
- href="/flow/editor/${event.flow.uuid}/"
173
- onclick="goto(event)"
174
- >
175
- ${event.flow.name}
176
- </div>
177
- `
184
+ ${scheduledEvent.flow
185
+ ? html` Start ${scheduledEvent.flow.name}`
178
186
  : null}
179
- ${event.message
180
- ? html` <div class="message">${event.message}</div> `
187
+ ${scheduledEvent.message
188
+ ? html` <div class="message">${scheduledEvent.message}</div> `
181
189
  : null}
182
190
  </div>
183
191
 
184
192
  <div class="scheduled-by">
185
- ${event.campaign
186
- ? html`<div
187
- style="display:flex"
188
- href="/campaign/read/${event.campaign.uuid}/"
189
- onclick="goto(event, this)"
190
- >
193
+ ${scheduledEvent.campaign
194
+ ? html`<div style="display:flex">
191
195
  <temba-icon name="${Icon.campaign}"></temba-icon>
192
- <div class="name">${event.campaign.name}</div>
196
+ <div class="name">${scheduledEvent.campaign.name}</div>
193
197
  </div>`
194
198
  : html`
195
- ${event.type === ScheduledEventType.ScheduledTrigger
199
+ ${scheduledEvent.type === ScheduledEventType.ScheduledTrigger
196
200
  ? html`<temba-icon
197
- name="${ICONS[event.type]}"
201
+ name="${ICONS[scheduledEvent.type]}"
198
202
  ></temba-icon>`
199
203
  : null}
200
204
  <div class="name">
201
- ${this.REPEAT_PERIOD[event.repeat_period]}
205
+ ${this.REPEAT_PERIOD[scheduledEvent.repeat_period]}
202
206
  </div>
203
207
  `}
204
208
  </div>
@@ -207,10 +211,10 @@ export class ContactPending extends StoreElement {
207
211
  <div class="time">
208
212
  <div class="duration">
209
213
  <temba-tip
210
- text=${this.store.formatDate(event.scheduled)}
214
+ text=${this.store.formatDate(scheduledEvent.scheduled)}
211
215
  position="left"
212
216
  >
213
- ${this.store.getShortDurationFromIso(event.scheduled)}
217
+ ${this.store.getShortDurationFromIso(scheduledEvent.scheduled)}
214
218
  </temba-tip>
215
219
  </div>
216
220
  </div>
@@ -239,8 +239,8 @@ export const getEventStyles = () => {
239
239
  .tickets .note-summary {
240
240
  display: flex;
241
241
  flex-direction: row;
242
- line-height: 0.5;
243
- font-size: 80%;
242
+ font-size: 85%;
243
+ margin-top: -0.5em;
244
244
  color: rgba(0, 0, 0, 0.6);
245
245
  padding: 8px 3px;
246
246
  }
@@ -310,12 +310,11 @@ export const getEventStyles = () => {
310
310
 
311
311
  .msg-summary {
312
312
  display: flex;
313
- line-height: 0.5;
314
-
315
- font-size: 80%;
313
+ font-size: 85%;
316
314
  color: rgba(0, 0, 0, 0.6);
317
315
  padding: 6px 3px;
318
316
  margin-bottom: 0.5em;
317
+ margin-top: -0.5em;
319
318
  }
320
319
 
321
320
  .msg-summary temba-icon.log {
@@ -776,7 +775,11 @@ export const renderMsgEvent = (
776
775
  <div class="separator">•</div>`);
777
776
  }
778
777
  summary.push(
779
- html`<div class="time">${timeSince(new Date(event.created_on))}</div>`
778
+ html`<temba-date
779
+ class="time"
780
+ value="${event.created_on}"
781
+ display="duration"
782
+ ></temba-date>`
780
783
  );
781
784
 
782
785
  return html`<div style="display:flex;align-items:flex-start">
@@ -938,7 +941,11 @@ export const renderNoteCreated = (
938
941
  <div class="description">${event.note}</div>
939
942
  <div class="note-summary">
940
943
  <div style="flex-grow:1"></div>
941
- <div class="time">${timeSince(new Date(event.created_on))}</div>
944
+ <temba-date
945
+ class="time"
946
+ value="${event.created_on}"
947
+ display="duration"
948
+ ></temba-date>
942
949
  </div>
943
950
  </div>
944
951
  <div style="margin-left:0.8em;margin-top:0.3em;font-size:0.8em">
@@ -986,14 +993,17 @@ export const renderTicketAction = (
986
993
  ${getDisplayName(event.created_by)} ${action} this ticket
987
994
  </div>
988
995
  <div class="subtext" style="justify-content:center">
989
- ${timeSince(reopened, { hideRecentText: true, suffix: ' ago' })}
996
+ <temba-date
997
+ class="time"
998
+ value="${reopened}"
999
+ display="duration"
1000
+ ></temba-date>
990
1001
  </div>
991
1002
  </div>
992
1003
  `;
993
1004
  };
994
1005
 
995
1006
  export const renderTicketAssigned = (event: TicketEvent): TemplateResult => {
996
- const created = new Date(event.created_on);
997
1007
  return html`
998
1008
  <div class="assigned active">
999
1009
  <div style="text-align:center">
@@ -1005,7 +1015,11 @@ export const renderTicketAssigned = (event: TicketEvent): TemplateResult => {
1005
1015
  : html`${getDisplayName(event.created_by)} unassigned this ticket`}
1006
1016
  </div>
1007
1017
  <div class="subtext" style="justify-content:center">
1008
- ${timeSince(created, { hideRecentText: true, suffix: ' ago' })}
1018
+ <temba-date
1019
+ class="time"
1020
+ value="${event.created_on}"
1021
+ display="duration"
1022
+ ></temba-date>
1009
1023
  </div>
1010
1024
  </div>
1011
1025
  `;
@@ -7,7 +7,11 @@ import { DateTime } from 'luxon';
7
7
  export const Display = {
8
8
  date: DateTime.DATE_SHORT,
9
9
  datetime: DateTime.DATETIME_SHORT,
10
+ time: DateTime.TIME_SIMPLE,
11
+ timedate: 'timedate',
10
12
  duration: 'duration',
13
+ relative: 'relative',
14
+ day: 'LLL d',
11
15
  };
12
16
 
13
17
  export class TembaDate extends RapidElement {
@@ -48,19 +52,44 @@ export class TembaDate extends RapidElement {
48
52
  }
49
53
 
50
54
  public render(): TemplateResult {
51
- if (this.datetime) {
52
- if (this.display === Display.duration) {
53
- return html`<div class="date">
54
- ${this.store.getShortDuration(this.datetime)}
55
- </div>`;
55
+ if (this.datetime && this.store) {
56
+ this.datetime.setLocale(this.store.getLocale());
57
+
58
+ let formatted = '';
59
+ if (this.display === Display.timedate) {
60
+ const hours = Math.abs(
61
+ this.datetime.diffNow().milliseconds / 1000 / 60 / 60
62
+ );
63
+ if (hours < 24) {
64
+ formatted = this.datetime.toLocaleString(Display.time);
65
+ } else if (hours < 24 * 365) {
66
+ formatted = this.datetime.toFormat(Display.day);
67
+ } else {
68
+ formatted = this.datetime.toLocaleString(Display.date);
69
+ }
70
+ } else if (this.display === Display.relative) {
71
+ const minutes = Math.abs(
72
+ this.datetime.diffNow().milliseconds / 1000 / 60
73
+ );
74
+ if (minutes < 1) {
75
+ return html`<div class="date">just now</div>`;
76
+ }
77
+
78
+ formatted = this.store.getShortDuration(this.datetime);
79
+ } else if (this.display === Display.duration) {
80
+ const minutes = Math.abs(
81
+ this.datetime.diffNow().milliseconds / 1000 / 60
82
+ );
83
+ if (minutes < 1) {
84
+ return html`<div class="date">just now</div>`;
85
+ }
86
+ formatted = this.store.getShortDuration(this.datetime);
87
+ } else if (this.display === Display.day) {
88
+ formatted = this.datetime.toLocaleString(Display.day);
56
89
  } else {
57
- return html`
58
- <div class="date">
59
- ${this.datetime.toLocaleString(Display[this.display])}
60
- </div>
61
- `;
90
+ formatted = this.datetime.toLocaleString(Display[this.display]);
62
91
  }
92
+ return html`<div class="date">${formatted}</div>`;
63
93
  }
64
- return null;
65
94
  }
66
95
  }
@@ -5,10 +5,19 @@ import { RapidElement } from '../RapidElement';
5
5
  export class Dropdown extends RapidElement {
6
6
  static get styles() {
7
7
  return css`
8
+ .wrapper {
9
+ position: relative;
10
+ }
11
+
8
12
  .toggle {
9
13
  cursor: pointer;
10
14
  }
11
15
 
16
+ .dropdown-wrapper {
17
+ position: relative;
18
+ overflow: auto;
19
+ }
20
+
12
21
  .dropdown {
13
22
  position: absolute;
14
23
  opacity: 0;
@@ -52,6 +61,9 @@ export class Dropdown extends RapidElement {
52
61
  @property({ type: Boolean })
53
62
  open = false;
54
63
 
64
+ @property({ type: String, attribute: 'drop_align' })
65
+ dropAlign = 'left';
66
+
55
67
  @property({ type: Number })
56
68
  arrowSize = 6;
57
69
 
@@ -96,6 +108,8 @@ export class Dropdown extends RapidElement {
96
108
  // a better way to deal with this
97
109
  window.setTimeout(() => {
98
110
  this.open = false;
111
+ // blur our host element too
112
+ (this.shadowRoot.host as HTMLDivElement).blur();
99
113
  }, 200);
100
114
  });
101
115
  }
@@ -111,7 +125,7 @@ export class Dropdown extends RapidElement {
111
125
  }
112
126
  }
113
127
 
114
- public handleOpen(): void {
128
+ public handleToggleClicked(): void {
115
129
  if (!this.open) {
116
130
  this.open = true;
117
131
 
@@ -124,11 +138,21 @@ export class Dropdown extends RapidElement {
124
138
 
125
139
  public render(): TemplateResult {
126
140
  return html`
127
- <div class=${this.open ? 'open' : ''}>
128
- <slot name="toggle" class="toggle" @click="${this.handleOpen}"></slot>
129
- <div class="dropdown" tabindex="0">
141
+ <div class="wrapper ${this.open ? 'open' : ''}">
142
+ <slot
143
+ name="toggle"
144
+ class="toggle"
145
+ @click="${this.handleToggleClicked}"
146
+ ></slot>
147
+ <div
148
+ class="dropdown"
149
+ tabindex="0"
150
+ style="${this.dropAlign == 'right' ? 'right:0' : ''}"
151
+ >
130
152
  <div class="arrow"></div>
131
- <slot name="dropdown"></slot>
153
+ <div class="dropdown-wrapper">
154
+ <slot name="dropdown" tabindex="1"></slot>
155
+ </div>
132
156
  </div>
133
157
  </div>
134
158
  `;
@@ -141,7 +141,7 @@ export class RunList extends TembaList {
141
141
  </div>
142
142
 
143
143
  <div style="flex-shrink:1">
144
- ${this.store.getShortDurationFromIso(run.modified_on)}
144
+ <temba-date value="${run.modified_on}" display="duration" />
145
145
  </div>
146
146
  ${this.getIcon(run)}
147
147
  </div>
@@ -227,7 +227,6 @@ export class RunList extends TembaList {
227
227
  return null;
228
228
  }
229
229
 
230
- const exitType = this.selectedRun.exit_type;
231
230
  const resultKeys = Object.keys(this.selectedRun.values);
232
231
 
233
232
  return html` <div
@@ -251,35 +250,25 @@ export class RunList extends TembaList {
251
250
  >
252
251
  ${this.selectedRun.exit_type
253
252
  ? html`
254
- ${this.getIcon(this.selectedRun)}
255
- <div style="margin-left:0.5em;flex-grow:1">
256
- ${capitalize(this.selectedRun.exit_type)}
257
- ${exitType == 'completed'
258
- ? html` in
259
- ${this.store.getShortDurationFromIso(
260
- this.selectedRun.created_on,
261
- this.selectedRun.exited_on,
262
- true
263
- )}`
264
- : null}
265
- ${exitType == 'interrupted' || exitType == 'expired'
266
- ? html` after
267
- ${this.store.getShortDurationFromIso(
268
- this.selectedRun.created_on,
269
- this.selectedRun.exited_on,
270
- true
271
- )}`
272
- : null}
253
+ <div style="margin-left:2em;flex-grow:1;display:flex">
254
+ ${this.getIcon(this.selectedRun)}
255
+ <div style="margin-left:0.5em">
256
+ ${capitalize(this.selectedRun.exit_type)}&nbsp;
257
+ </div>
258
+ <temba-date
259
+ value="${this.selectedRun.exited_on}"
260
+ compare="${this.selectedRun.created_on}"
261
+ display="duration"
262
+ />
273
263
  </div>
274
264
  `
275
265
  : html`${this.getIcon(this.selectedRun)}
276
- <div style="margin-left:0.5em;flex-grow:1">
277
- Active for
278
- ${this.store.getShortDurationFromIso(
279
- this.selectedRun.created_on,
280
- null,
281
- true
282
- )}
266
+ <div style="margin-left:1.5em;flex-grow:1;display:flex">
267
+ <div>Started&nbsp;</div>
268
+ <temba-date
269
+ value="${this.selectedRun.created_on}"
270
+ display="duration"
271
+ />
283
272
  </div>`}
284
273
  </div>
285
274
  </div>
@@ -74,6 +74,10 @@ export class Store extends RapidElement {
74
74
 
75
75
  private cache: any;
76
76
 
77
+ public getLocale() {
78
+ return this.locale[0];
79
+ }
80
+
77
81
  public reset() {
78
82
  this.cache = Lru(this.max, this.ttl);
79
83
 
@@ -192,41 +196,17 @@ export class Store extends RapidElement {
192
196
  });
193
197
  }
194
198
 
195
- public getShortDuration(
196
- scheduled: DateTime,
197
- compareDate: DateTime = null,
198
- showSeconds = false
199
- ) {
199
+ public getShortDuration(scheduled: DateTime, compareDate: DateTime = null) {
200
200
  const now = compareDate || DateTime.now();
201
- const duration = scheduled.diff(now).valueOf();
202
-
203
- if (showSeconds) {
204
- return this.humanizer.humanize(duration, {
205
- language: this.getLanguageCode(),
206
- largest: 1,
207
- round: true,
208
- });
209
- }
210
-
211
- if (Math.abs(duration) < 60000) {
212
- return 'just now';
213
- }
214
-
215
- return this.humanizer.humanize(duration, {
216
- language: this.getLanguageCode(),
217
- largest: 1,
218
- round: false,
219
- });
201
+ return scheduled
202
+ .setLocale(this.locale[0])
203
+ .toRelative({ base: now, style: 'long' });
220
204
  }
221
205
 
222
- public getShortDurationFromIso(
223
- isoDateA: string,
224
- isoDateB: string = null,
225
- showSeconds = false
226
- ) {
206
+ public getShortDurationFromIso(isoDateA: string, isoDateB: string = null) {
227
207
  const scheduled = DateTime.fromISO(isoDateA);
228
208
  const now = isoDateB ? DateTime.fromISO(isoDateB) : DateTime.now();
229
- return this.getShortDuration(scheduled, now, showSeconds);
209
+ return this.getShortDuration(scheduled, now);
230
210
  }
231
211
 
232
212
  public setKeyedAssets(name: string, values: string[]): void {
@@ -278,13 +258,9 @@ export class Store extends RapidElement {
278
258
  }
279
259
 
280
260
  public formatDate(dateString: string) {
281
- return new Date(dateString).toLocaleString(this.locale, {
282
- year: 'numeric',
283
- month: 'long',
284
- day: 'numeric',
285
- hour: 'numeric',
286
- minute: 'numeric',
287
- });
261
+ return DateTime.fromISO(dateString)
262
+ .setLocale(this.getLocale())
263
+ .toLocaleString(DateTime.DATETIME_SHORT);
288
264
  }
289
265
 
290
266
  public postUrl(
@@ -1,4 +1,5 @@
1
1
  export enum Icon {
2
+ analytics = 'bar-chart-01',
2
3
  account = 'user-01',
3
4
  active = 'play',
4
5
  add_note = 'file-02',
@@ -55,9 +56,11 @@ export enum Icon {
55
56
  org_new = 'stars-02',
56
57
  org_suspended = 'slash-circle-01',
57
58
  org_verified = 'check-verified-02',
59
+ overview = 'pie-chart-01',
58
60
  featured = 'star-01',
59
61
  resthooks = 'share-07',
60
62
  restore = 'play',
63
+ runs = 'rows-03',
61
64
  search = 'search-refraction',
62
65
  select_open = 'chevron-down',
63
66
  select_clear = 'x',