@nyaruka/temba-components 0.102.2 → 0.104.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.
- package/CHANGELOG.md +14 -0
- package/dist/temba-components.js +172 -70
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/contacts/ContactChat.js +22 -8
- package/out-tsc/src/contacts/ContactChat.js.map +1 -1
- package/out-tsc/src/contacts/ContactFields.js +5 -0
- package/out-tsc/src/contacts/ContactFields.js.map +1 -1
- package/out-tsc/src/contacts/ContactNotepad.js +129 -0
- package/out-tsc/src/contacts/ContactNotepad.js.map +1 -0
- package/out-tsc/src/contacts/ContactPending.js +7 -2
- package/out-tsc/src/contacts/ContactPending.js.map +1 -1
- package/out-tsc/src/contacts/ContactStoreElement.js +11 -2
- package/out-tsc/src/contacts/ContactStoreElement.js.map +1 -1
- package/out-tsc/src/contacts/ContactTickets.js +7 -2
- package/out-tsc/src/contacts/ContactTickets.js.map +1 -1
- package/out-tsc/src/contacts/events.js.map +1 -1
- package/out-tsc/src/contacts/helpers.js +3 -0
- package/out-tsc/src/contacts/helpers.js.map +1 -1
- package/out-tsc/src/fields/FieldManager.js +2 -2
- package/out-tsc/src/fields/FieldManager.js.map +1 -1
- package/out-tsc/src/flow/FlowStoreElement.js +2 -2
- package/out-tsc/src/flow/FlowStoreElement.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/TembaMenu.js +10 -0
- package/out-tsc/src/list/TembaMenu.js.map +1 -1
- package/out-tsc/src/store/{StoreElement.js → EndpointMonitorElement.js} +5 -5
- package/out-tsc/src/store/EndpointMonitorElement.js.map +1 -0
- package/out-tsc/src/store/Store.js +22 -0
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/store/StoreMonitorElement.js +22 -0
- package/out-tsc/src/store/StoreMonitorElement.js.map +1 -1
- package/out-tsc/src/tabpane/Tab.js +33 -0
- package/out-tsc/src/tabpane/Tab.js.map +1 -1
- package/out-tsc/src/tabpane/TabPane.js +53 -15
- package/out-tsc/src/tabpane/TabPane.js.map +1 -1
- package/out-tsc/src/vectoricon/index.js +1 -0
- package/out-tsc/src/vectoricon/index.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/compose/attachments-and-send-button.png +0 -0
- package/screenshots/truth/compose/attachments-no-send-button.png +0 -0
- package/screenshots/truth/compose/attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/attachments-with-all-files.png +0 -0
- package/screenshots/truth/compose/attachments-with-failure-files.png +0 -0
- package/screenshots/truth/compose/attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/attachments-with-success-files.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-no-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-no-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-no-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-no-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-failure-files.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-spaces.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-url.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-no-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-failure-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-no-spaces.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text.png +0 -0
- package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
- package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
- package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
- package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
- package/screenshots/truth/contacts/contact-active-default.png +0 -0
- package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
- package/src/contacts/ContactChat.ts +21 -10
- package/src/contacts/ContactFields.ts +7 -0
- package/src/contacts/ContactNotepad.ts +133 -0
- package/src/contacts/ContactPending.ts +8 -2
- package/src/contacts/ContactStoreElement.ts +12 -2
- package/src/contacts/ContactTickets.ts +9 -2
- package/src/contacts/events.ts +0 -1
- package/src/contacts/helpers.ts +5 -1
- package/src/fields/FieldManager.ts +2 -2
- package/src/flow/FlowStoreElement.ts +2 -2
- package/src/interfaces.ts +13 -1
- package/src/list/TembaMenu.ts +12 -0
- package/src/store/{StoreElement.ts → EndpointMonitorElement.ts} +1 -4
- package/src/store/Store.ts +31 -0
- package/src/store/StoreMonitorElement.ts +24 -0
- package/src/tabpane/Tab.ts +29 -0
- package/src/tabpane/TabPane.ts +54 -15
- package/src/vectoricon/index.ts +1 -0
- package/temba-modules.ts +2 -0
- package/out-tsc/src/store/StoreElement.js.map +0 -1
- package/screenshots/truth/contacts/history.png +0 -0
|
@@ -4,6 +4,7 @@ import { getClasses, postJSON } from '../utils';
|
|
|
4
4
|
import { ContactFieldEditor } from './ContactFieldEditor';
|
|
5
5
|
import { ContactStoreElement } from './ContactStoreElement';
|
|
6
6
|
import { Checkbox } from '../checkbox/Checkbox';
|
|
7
|
+
import { CustomEventType } from '../interfaces';
|
|
7
8
|
|
|
8
9
|
const MIN_FOR_FILTER = 10;
|
|
9
10
|
|
|
@@ -104,12 +105,18 @@ export class ContactFields extends ContactStoreElement {
|
|
|
104
105
|
if (Object.keys(this.data.fields).length <= MIN_FOR_FILTER) {
|
|
105
106
|
this.showAll = true;
|
|
106
107
|
}
|
|
108
|
+
|
|
109
|
+
this.fireCustomEvent(CustomEventType.DetailsChanged, {
|
|
110
|
+
count: Object.values(this.data.fields).filter((value) => !!value).length
|
|
111
|
+
});
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
public handleFieldChanged(evt: InputEvent) {
|
|
111
116
|
const field = evt.currentTarget as ContactFieldEditor;
|
|
112
117
|
const value = field.value;
|
|
118
|
+
|
|
119
|
+
// TODO: Use contact.postChanges instead of postJSON
|
|
113
120
|
postJSON('/api/v2/contacts.json?uuid=' + this.data.uuid, {
|
|
114
121
|
fields: { [field.key]: value }
|
|
115
122
|
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { css, html, PropertyValueMap, TemplateResult } from 'lit';
|
|
2
|
+
import { property } from 'lit/decorators.js';
|
|
3
|
+
import { ContactStoreElement } from './ContactStoreElement';
|
|
4
|
+
import { getDisplayName } from './helpers';
|
|
5
|
+
import { ContactNote, CustomEventType } from '../interfaces';
|
|
6
|
+
|
|
7
|
+
export class ContactNotepad extends ContactStoreElement {
|
|
8
|
+
@property({ type: Object, attribute: false })
|
|
9
|
+
note: ContactNote;
|
|
10
|
+
|
|
11
|
+
@property({ type: String })
|
|
12
|
+
dirtyMessage =
|
|
13
|
+
'You have unsaved changes to the contact notepad. Are you sure you want to contiunue?';
|
|
14
|
+
|
|
15
|
+
static get styles() {
|
|
16
|
+
return css`
|
|
17
|
+
:host {
|
|
18
|
+
height: 100%;
|
|
19
|
+
display: flex;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.wrapper {
|
|
23
|
+
flex-grow: 1;
|
|
24
|
+
--color-widget-bg: transparent;
|
|
25
|
+
--color-widget-bg-focused: transparent;
|
|
26
|
+
outline: none;
|
|
27
|
+
border-radius: var(--curvature);
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.notepad {
|
|
33
|
+
flex-grow: 1;
|
|
34
|
+
padding: 1.25rem;
|
|
35
|
+
overflow-y: auto;
|
|
36
|
+
background: transparent;
|
|
37
|
+
border: none;
|
|
38
|
+
outline: none;
|
|
39
|
+
resize: none;
|
|
40
|
+
font-family: monospace;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.notepad:focus {
|
|
44
|
+
outline: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.toolbar {
|
|
48
|
+
background: rgba(0, 0, 0, 0.03);
|
|
49
|
+
padding: 0.25em 0.5em;
|
|
50
|
+
display: flex;
|
|
51
|
+
min-height: 2em;
|
|
52
|
+
align-items: center;
|
|
53
|
+
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.toolbar temba-button {
|
|
57
|
+
margin-right: 0.5em;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.updated {
|
|
61
|
+
font-size: 0.9em;
|
|
62
|
+
flex-grow: 1;
|
|
63
|
+
margin-left: 0.5em;
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private handleChange() {
|
|
69
|
+
this.markDirty();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private submitChanges() {
|
|
73
|
+
const notepad = this.shadowRoot.querySelector(
|
|
74
|
+
'.notepad'
|
|
75
|
+
) as HTMLInputElement;
|
|
76
|
+
const note = notepad.value;
|
|
77
|
+
this.postChanges({ note }).then(() => {
|
|
78
|
+
this.markClean();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected updated(
|
|
83
|
+
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
84
|
+
): void {
|
|
85
|
+
super.updated(changes);
|
|
86
|
+
|
|
87
|
+
if (changes.has('data')) {
|
|
88
|
+
this.note =
|
|
89
|
+
this.data?.notes.length > 0
|
|
90
|
+
? { ...this.data.notes[this.data.notes.length - 1] }
|
|
91
|
+
: null;
|
|
92
|
+
this.fireCustomEvent(CustomEventType.DetailsChanged, {
|
|
93
|
+
count: this.note && this.note.text.length > 0 ? 1 : 0
|
|
94
|
+
});
|
|
95
|
+
this.markClean();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public render(): TemplateResult {
|
|
100
|
+
if (this.data) {
|
|
101
|
+
return html`
|
|
102
|
+
<div class="wrapper">
|
|
103
|
+
<!-- prettier-ignore -->
|
|
104
|
+
<textarea class="notepad" @input=${this.handleChange} .value=${this
|
|
105
|
+
.note
|
|
106
|
+
? this.note.text
|
|
107
|
+
: ''}></textarea>
|
|
108
|
+
<div class="toolbar">
|
|
109
|
+
<div class="updated">
|
|
110
|
+
${this.note
|
|
111
|
+
? html`Last updated by ${getDisplayName(this.note.created_by)}
|
|
112
|
+
<temba-date
|
|
113
|
+
value="${this.note.created_on}"
|
|
114
|
+
display="duration"
|
|
115
|
+
></temba-date>`
|
|
116
|
+
: null}
|
|
117
|
+
</div>
|
|
118
|
+
${this.dirty
|
|
119
|
+
? html`
|
|
120
|
+
<temba-button
|
|
121
|
+
name="Save"
|
|
122
|
+
small
|
|
123
|
+
@click=${this.submitChanges}
|
|
124
|
+
></temba-button>
|
|
125
|
+
`
|
|
126
|
+
: null}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
return super.render();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ScheduledEvent,
|
|
6
6
|
ScheduledEventType
|
|
7
7
|
} from '../interfaces';
|
|
8
|
-
import {
|
|
8
|
+
import { EndpointMonitorElement } from '../store/EndpointMonitorElement';
|
|
9
9
|
import { Icon } from '../vectoricon';
|
|
10
10
|
|
|
11
11
|
const ICONS = {
|
|
@@ -14,7 +14,7 @@ const ICONS = {
|
|
|
14
14
|
[ScheduledEventType.ScheduledTrigger]: Icon.trigger
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export class ContactPending extends
|
|
17
|
+
export class ContactPending extends EndpointMonitorElement {
|
|
18
18
|
@property({ type: String })
|
|
19
19
|
contact: string;
|
|
20
20
|
|
|
@@ -160,6 +160,12 @@ export class ContactPending extends StoreElement {
|
|
|
160
160
|
this.url = null;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
if (changes.has('data')) {
|
|
165
|
+
this.fireCustomEvent(CustomEventType.DetailsChanged, {
|
|
166
|
+
count: this.data.length
|
|
167
|
+
});
|
|
168
|
+
}
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
public handleEventClicked(event: ScheduledEvent) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PropertyValueMap } from 'lit';
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { Contact, Group } from '../interfaces';
|
|
4
|
-
import {
|
|
4
|
+
import { EndpointMonitorElement } from '../store/EndpointMonitorElement';
|
|
5
5
|
|
|
6
|
-
export class ContactStoreElement extends
|
|
6
|
+
export class ContactStoreElement extends EndpointMonitorElement {
|
|
7
7
|
@property({ type: String })
|
|
8
8
|
contact: string;
|
|
9
9
|
|
|
@@ -39,6 +39,16 @@ export class ContactStoreElement extends StoreElement {
|
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
public postChanges(payload: any) {
|
|
43
|
+
// clear our cache so we don't have any races
|
|
44
|
+
this.store.removeFromCache(`${this.endpoint}${this.contact}`);
|
|
45
|
+
return this.store
|
|
46
|
+
.postJSON(`${this.endpoint}${this.contact}`, payload)
|
|
47
|
+
.then((response) => {
|
|
48
|
+
this.setContact(response.json);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
public setContact(contact: any) {
|
|
43
53
|
// make sure contact data is properly prepped
|
|
44
54
|
this.data = this.prepareData([contact]);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { css, html, PropertyValueMap, TemplateResult } from 'lit';
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { CustomEventType, Ticket, TicketStatus } from '../interfaces';
|
|
4
|
-
import {
|
|
4
|
+
import { EndpointMonitorElement } from '../store/EndpointMonitorElement';
|
|
5
5
|
import { getClasses, postJSON, stopEvent } from '../utils';
|
|
6
6
|
import { Icon } from '../vectoricon';
|
|
7
7
|
|
|
8
8
|
const dropdownUserScale = 0.7;
|
|
9
9
|
const inlineUserScale = 0.8;
|
|
10
10
|
|
|
11
|
-
export class ContactTickets extends
|
|
11
|
+
export class ContactTickets extends EndpointMonitorElement {
|
|
12
12
|
@property({ type: String })
|
|
13
13
|
agent: string;
|
|
14
14
|
|
|
@@ -230,6 +230,13 @@ export class ContactTickets extends StoreElement {
|
|
|
230
230
|
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
231
231
|
): void {
|
|
232
232
|
super.updated(changes);
|
|
233
|
+
|
|
234
|
+
if (changes.has('data')) {
|
|
235
|
+
this.fireCustomEvent(CustomEventType.DetailsChanged, {
|
|
236
|
+
count: this.data.length
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
233
240
|
if (changes.has('contact') || changes.has('ticket')) {
|
|
234
241
|
if (this.contact) {
|
|
235
242
|
this.url = `/api/v2/tickets.json?contact=${this.contact}${
|
package/src/contacts/events.ts
CHANGED
package/src/contacts/helpers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Contact, User } from '../interfaces';
|
|
1
|
+
import { Contact, NamedUser, User } from '../interfaces';
|
|
2
2
|
import { fetchResults, getUrl, postUrl, WebResponse } from '../utils';
|
|
3
3
|
import { ContactHistoryPage } from './events';
|
|
4
4
|
|
|
@@ -80,6 +80,10 @@ export const getDisplayName = (user: User) => {
|
|
|
80
80
|
return 'Somebody';
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
if ((user as NamedUser).name) {
|
|
84
|
+
return (user as NamedUser).name;
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
if (user.first_name && user.last_name) {
|
|
84
88
|
return `${user.first_name} ${user.last_name}`;
|
|
85
89
|
}
|
|
@@ -3,7 +3,7 @@ import { property } from 'lit/decorators.js';
|
|
|
3
3
|
import { ContactField, CustomEventType } from '../interfaces';
|
|
4
4
|
|
|
5
5
|
import { SortableList } from '../list/SortableList';
|
|
6
|
-
import {
|
|
6
|
+
import { EndpointMonitorElement } from '../store/EndpointMonitorElement';
|
|
7
7
|
import { postJSON } from '../utils';
|
|
8
8
|
|
|
9
9
|
const TYPE_NAMES = {
|
|
@@ -31,7 +31,7 @@ const matches = (field: ContactField, query: string): boolean => {
|
|
|
31
31
|
return false;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
export class FieldManager extends
|
|
34
|
+
export class FieldManager extends EndpointMonitorElement {
|
|
35
35
|
static get styles() {
|
|
36
36
|
return css`
|
|
37
37
|
:host {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { html, PropertyValueMap, TemplateResult } from 'lit';
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { FlowDetails } from '../interfaces';
|
|
4
|
-
import {
|
|
4
|
+
import { EndpointMonitorElement } from '../store/EndpointMonitorElement';
|
|
5
5
|
|
|
6
|
-
export class FlowStoreElement extends
|
|
6
|
+
export class FlowStoreElement extends EndpointMonitorElement {
|
|
7
7
|
@property({ type: String })
|
|
8
8
|
flow: string;
|
|
9
9
|
|
package/src/interfaces.ts
CHANGED
|
@@ -48,6 +48,10 @@ export interface ScheduledEvent {
|
|
|
48
48
|
message?: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface NamedUser extends User {
|
|
52
|
+
name: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
export interface User {
|
|
52
56
|
id?: number;
|
|
53
57
|
first_name?: string;
|
|
@@ -136,6 +140,12 @@ export interface Group {
|
|
|
136
140
|
is_dynamic?: boolean;
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
export interface ContactNote {
|
|
144
|
+
text: string;
|
|
145
|
+
created_on: string;
|
|
146
|
+
created_by: NamedUser;
|
|
147
|
+
}
|
|
148
|
+
|
|
139
149
|
export interface ContactTicket {
|
|
140
150
|
name: string;
|
|
141
151
|
uuid: string;
|
|
@@ -158,6 +168,7 @@ export interface Contact {
|
|
|
158
168
|
language?: string;
|
|
159
169
|
fields: { [key: string]: string };
|
|
160
170
|
groups: Group[];
|
|
171
|
+
notes: ContactNote[];
|
|
161
172
|
modified_on: string;
|
|
162
173
|
created_on: string;
|
|
163
174
|
last_seen_on: string;
|
|
@@ -260,5 +271,6 @@ export enum CustomEventType {
|
|
|
260
271
|
OrderChanged = 'temba-order-changed',
|
|
261
272
|
DragStart = 'temba-drag-start',
|
|
262
273
|
DragStop = 'temba-drag-stop',
|
|
263
|
-
Resized = 'temba-resized'
|
|
274
|
+
Resized = 'temba-resized',
|
|
275
|
+
DetailsChanged = 'temba-details-changed'
|
|
264
276
|
}
|
package/src/list/TembaMenu.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Icon } from '../vectoricon';
|
|
|
7
7
|
import { Dropdown } from '../dropdown/Dropdown';
|
|
8
8
|
import { NotificationList } from './NotificationList';
|
|
9
9
|
import { ResizeElement } from '../ResizeElement';
|
|
10
|
+
import { Store } from '../store/Store';
|
|
10
11
|
export interface MenuItem {
|
|
11
12
|
id?: string;
|
|
12
13
|
vanity_id?: string;
|
|
@@ -820,6 +821,17 @@ export class TembaMenu extends ResizeElement {
|
|
|
820
821
|
menuItem: MenuItem,
|
|
821
822
|
parent: MenuItem = null
|
|
822
823
|
) {
|
|
824
|
+
const store = document.querySelector('temba-store') as Store;
|
|
825
|
+
if (store) {
|
|
826
|
+
const unsavedMessage = store.getDirtyMessage();
|
|
827
|
+
if (unsavedMessage) {
|
|
828
|
+
if (!confirm(unsavedMessage)) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
store.cleanAll();
|
|
833
|
+
}
|
|
834
|
+
|
|
823
835
|
if (parent && parent.popup) {
|
|
824
836
|
const dropdown = this.shadowRoot.querySelector(
|
|
825
837
|
'temba-dropdown'
|
|
@@ -2,14 +2,13 @@ import { PropertyValueMap } from 'lit';
|
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { CustomEventType } from '../interfaces';
|
|
4
4
|
|
|
5
|
-
import { Store } from './Store';
|
|
6
5
|
import { StoreMonitorElement } from './StoreMonitorElement';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* StoreElement is a listener for a given endpoint that re-renders
|
|
10
9
|
* when the underlying store element changes
|
|
11
10
|
*/
|
|
12
|
-
export class
|
|
11
|
+
export class EndpointMonitorElement extends StoreMonitorElement {
|
|
13
12
|
@property({ type: String })
|
|
14
13
|
url: string;
|
|
15
14
|
|
|
@@ -19,8 +18,6 @@ export class StoreElement extends StoreMonitorElement {
|
|
|
19
18
|
@property({ type: Object, attribute: false })
|
|
20
19
|
data: any;
|
|
21
20
|
|
|
22
|
-
store: Store;
|
|
23
|
-
|
|
24
21
|
prepareData(data: any): any {
|
|
25
22
|
return data;
|
|
26
23
|
}
|
package/src/store/Store.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { DateTime } from 'luxon';
|
|
|
26
26
|
import { css, html } from 'lit';
|
|
27
27
|
import { configureLocalization } from '@lit/localize';
|
|
28
28
|
import { sourceLocale, targetLocales } from '../locales/locale-codes';
|
|
29
|
+
import { StoreMonitorElement } from './StoreMonitorElement';
|
|
29
30
|
|
|
30
31
|
const { setLocale } = configureLocalization({
|
|
31
32
|
sourceLocale,
|
|
@@ -105,6 +106,32 @@ export class Store extends RapidElement {
|
|
|
105
106
|
// http promise to monitor for completeness
|
|
106
107
|
public initialHttpComplete: Promise<void | WebResponse[]>;
|
|
107
108
|
|
|
109
|
+
private dirtyElements: StoreMonitorElement[] = [];
|
|
110
|
+
|
|
111
|
+
public markDirty(ele: StoreMonitorElement) {
|
|
112
|
+
if (!this.dirtyElements.includes(ele)) {
|
|
113
|
+
this.dirtyElements.push(ele);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public cleanAll() {
|
|
118
|
+
this.dirtyElements.forEach((ele) => ele.markClean());
|
|
119
|
+
this.dirtyElements = [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public markClean(ele: StoreMonitorElement) {
|
|
123
|
+
this.dirtyElements = this.dirtyElements.filter((el) => el !== ele);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public getDirtyMessage() {
|
|
127
|
+
if (this.dirtyElements.length > 0) {
|
|
128
|
+
return (
|
|
129
|
+
this.dirtyElements[0].dirtyMessage ||
|
|
130
|
+
'You have unsaved changes, are you sure you want to continue?'
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
private cache: any;
|
|
109
136
|
public getLocale() {
|
|
110
137
|
return this.locale[0];
|
|
@@ -436,6 +463,10 @@ export class Store extends RapidElement {
|
|
|
436
463
|
this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data });
|
|
437
464
|
}
|
|
438
465
|
|
|
466
|
+
public removeFromCache(url: string) {
|
|
467
|
+
this.cache.delete(url);
|
|
468
|
+
}
|
|
469
|
+
|
|
439
470
|
public makeRequest(
|
|
440
471
|
url: string,
|
|
441
472
|
options?: { force?: boolean; prepareData?: (data: any) => any }
|
|
@@ -14,8 +14,30 @@ export class StoreMonitorElement extends RapidElement {
|
|
|
14
14
|
@property({ type: Boolean })
|
|
15
15
|
showLoading = false;
|
|
16
16
|
|
|
17
|
+
@property({ type: Boolean })
|
|
18
|
+
dirty = false;
|
|
19
|
+
|
|
20
|
+
@property({ type: String })
|
|
21
|
+
dirtyMessage: string;
|
|
22
|
+
|
|
17
23
|
store: Store;
|
|
18
24
|
|
|
25
|
+
markDirty() {
|
|
26
|
+
this.dirty = true;
|
|
27
|
+
this.store.markDirty(this);
|
|
28
|
+
this.fireCustomEvent(CustomEventType.DetailsChanged, {
|
|
29
|
+
dirty: true
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
markClean() {
|
|
34
|
+
this.dirty = false;
|
|
35
|
+
this.store.markClean(this);
|
|
36
|
+
this.fireCustomEvent(CustomEventType.DetailsChanged, {
|
|
37
|
+
dirty: false
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
private handleStoreUpdated(event: CustomEvent) {
|
|
20
42
|
this.store.initialHttpComplete.then(() => {
|
|
21
43
|
this.storeUpdated(event);
|
|
@@ -50,6 +72,8 @@ export class StoreMonitorElement extends RapidElement {
|
|
|
50
72
|
CustomEventType.StoreUpdated,
|
|
51
73
|
this.handleStoreUpdated
|
|
52
74
|
);
|
|
75
|
+
|
|
76
|
+
this.store.markClean(this);
|
|
53
77
|
}
|
|
54
78
|
}
|
|
55
79
|
|
package/src/tabpane/Tab.ts
CHANGED
|
@@ -31,6 +31,12 @@ export class Tab extends RapidElement {
|
|
|
31
31
|
@property({ type: String })
|
|
32
32
|
selectionBackground: string;
|
|
33
33
|
|
|
34
|
+
@property({ type: String })
|
|
35
|
+
borderColor: string = 'var(--color-widget-border)';
|
|
36
|
+
|
|
37
|
+
@property({ type: String })
|
|
38
|
+
activityColor: string = `var(--color-link-primary)`;
|
|
39
|
+
|
|
34
40
|
@property({ type: Boolean })
|
|
35
41
|
selected = false;
|
|
36
42
|
|
|
@@ -43,12 +49,22 @@ export class Tab extends RapidElement {
|
|
|
43
49
|
@property({ type: Boolean })
|
|
44
50
|
hidden = false;
|
|
45
51
|
|
|
52
|
+
@property({ type: Boolean })
|
|
53
|
+
hideEmpty = false;
|
|
54
|
+
|
|
55
|
+
// show just that there is activity instead of count
|
|
56
|
+
@property({ type: Boolean })
|
|
57
|
+
activity = false;
|
|
58
|
+
|
|
46
59
|
@property({ type: Number })
|
|
47
60
|
count = 0;
|
|
48
61
|
|
|
49
62
|
@property({ type: Boolean })
|
|
50
63
|
checked = false;
|
|
51
64
|
|
|
65
|
+
@property({ type: Boolean })
|
|
66
|
+
dirty = false;
|
|
67
|
+
|
|
52
68
|
public updated(
|
|
53
69
|
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
54
70
|
): void {
|
|
@@ -62,8 +78,21 @@ export class Tab extends RapidElement {
|
|
|
62
78
|
return this.count > 0;
|
|
63
79
|
}
|
|
64
80
|
|
|
81
|
+
public handleDetailsChanged(event: CustomEvent) {
|
|
82
|
+
if ('dirty' in event.detail) {
|
|
83
|
+
this.dirty = event.detail.dirty;
|
|
84
|
+
}
|
|
85
|
+
if ('count' in event.detail) {
|
|
86
|
+
this.count = event.detail.count;
|
|
87
|
+
if (this.hideEmpty) {
|
|
88
|
+
this.hidden = this.count === 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
public render(): TemplateResult {
|
|
66
94
|
return html`<slot
|
|
95
|
+
@temba-details-changed=${this.handleDetailsChanged}
|
|
67
96
|
class="${getClasses({ selected: this.selected })}"
|
|
68
97
|
></slot> `;
|
|
69
98
|
}
|