rn-erxes-sdk 0.1.22 → 0.1.24
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/README.md +40 -2
- package/lib/commonjs/App.js +41 -21
- package/lib/commonjs/App.js.map +1 -1
- package/lib/commonjs/Widget.js +158 -31
- package/lib/commonjs/Widget.js.map +1 -1
- package/lib/commonjs/components/Avatar.js +24 -27
- package/lib/commonjs/components/Avatar.js.map +1 -1
- package/lib/commonjs/components/AvatarWithStatus.js +56 -0
- package/lib/commonjs/components/AvatarWithStatus.js.map +1 -0
- package/lib/commonjs/components/ConversationItem.js +161 -0
- package/lib/commonjs/components/ConversationItem.js.map +1 -0
- package/lib/commonjs/components/Icons.js +558 -0
- package/lib/commonjs/components/Icons.js.map +1 -0
- package/lib/commonjs/components/InputTools.js +329 -38
- package/lib/commonjs/components/InputTools.js.map +1 -1
- package/lib/commonjs/components/MessengerShell.js +64 -0
- package/lib/commonjs/components/MessengerShell.js.map +1 -0
- package/lib/commonjs/components/conversation/DateSeparator.js +47 -0
- package/lib/commonjs/components/conversation/DateSeparator.js.map +1 -0
- package/lib/commonjs/components/conversation/EmojiPicker.js +62 -0
- package/lib/commonjs/components/conversation/EmojiPicker.js.map +1 -0
- package/lib/commonjs/components/conversation/PersistentMenu.js +64 -0
- package/lib/commonjs/components/conversation/PersistentMenu.js.map +1 -0
- package/lib/commonjs/components/conversation/TypingStatus.js +122 -0
- package/lib/commonjs/components/conversation/TypingStatus.js.map +1 -0
- package/lib/commonjs/components/conversation/WelcomeMessage.js +85 -0
- package/lib/commonjs/components/conversation/WelcomeMessage.js.map +1 -0
- package/lib/commonjs/components/nav/Navigation.js +130 -0
- package/lib/commonjs/components/nav/Navigation.js.map +1 -0
- package/lib/commonjs/components/nav/constants.js +23 -0
- package/lib/commonjs/components/nav/constants.js.map +1 -0
- package/lib/commonjs/graphql/ApolloContainer.js +1 -2
- package/lib/commonjs/graphql/ApolloContainer.js.map +1 -1
- package/lib/commonjs/graphql/mutation.js +58 -23
- package/lib/commonjs/graphql/mutation.js.map +1 -1
- package/lib/commonjs/graphql/query.js +85 -55
- package/lib/commonjs/graphql/query.js.map +1 -1
- package/lib/commonjs/graphql/subscription.js +32 -28
- package/lib/commonjs/graphql/subscription.js.map +1 -1
- package/lib/commonjs/screen/conversation/Attachment.js +77 -12
- package/lib/commonjs/screen/conversation/Attachment.js.map +1 -1
- package/lib/commonjs/screen/conversation/ConversationDetail.js +307 -136
- package/lib/commonjs/screen/conversation/ConversationDetail.js.map +1 -1
- package/lib/commonjs/screen/conversation/Message.js +145 -82
- package/lib/commonjs/screen/conversation/Message.js.map +1 -1
- package/lib/commonjs/screen/faq/Faq.js +373 -0
- package/lib/commonjs/screen/faq/Faq.js.map +1 -0
- package/lib/commonjs/screen/greetings/Social.js +19 -9
- package/lib/commonjs/screen/greetings/Social.js.map +1 -1
- package/lib/commonjs/screen/greetings/Supporters.js +49 -46
- package/lib/commonjs/screen/greetings/Supporters.js.map +1 -1
- package/lib/commonjs/screen/home/Home.js +246 -0
- package/lib/commonjs/screen/home/Home.js.map +1 -0
- package/lib/commonjs/screen/messages/Messages.js +312 -0
- package/lib/commonjs/screen/messages/Messages.js.map +1 -0
- package/lib/commonjs/theme.js +132 -0
- package/lib/commonjs/theme.js.map +1 -0
- package/lib/commonjs/utils/messages.js +93 -0
- package/lib/commonjs/utils/messages.js.map +1 -0
- package/lib/commonjs/utils/onlineHours.js +127 -0
- package/lib/commonjs/utils/onlineHours.js.map +1 -0
- package/lib/commonjs/utils/upload.js +245 -0
- package/lib/commonjs/utils/upload.js.map +1 -0
- package/lib/module/App.js +41 -21
- package/lib/module/App.js.map +1 -1
- package/lib/module/Widget.js +161 -34
- package/lib/module/Widget.js.map +1 -1
- package/lib/module/components/Avatar.js +25 -28
- package/lib/module/components/Avatar.js.map +1 -1
- package/lib/module/components/AvatarWithStatus.js +47 -0
- package/lib/module/components/AvatarWithStatus.js.map +1 -0
- package/lib/module/components/ConversationItem.js +153 -0
- package/lib/module/components/ConversationItem.js.map +1 -0
- package/lib/module/components/Icons.js +537 -0
- package/lib/module/components/Icons.js.map +1 -0
- package/lib/module/components/InputTools.js +331 -39
- package/lib/module/components/InputTools.js.map +1 -1
- package/lib/module/components/MessengerShell.js +55 -0
- package/lib/module/components/MessengerShell.js.map +1 -0
- package/lib/module/components/conversation/DateSeparator.js +40 -0
- package/lib/module/components/conversation/DateSeparator.js.map +1 -0
- package/lib/module/components/conversation/EmojiPicker.js +55 -0
- package/lib/module/components/conversation/EmojiPicker.js.map +1 -0
- package/lib/module/components/conversation/PersistentMenu.js +57 -0
- package/lib/module/components/conversation/PersistentMenu.js.map +1 -0
- package/lib/module/components/conversation/TypingStatus.js +113 -0
- package/lib/module/components/conversation/TypingStatus.js.map +1 -0
- package/lib/module/components/conversation/WelcomeMessage.js +76 -0
- package/lib/module/components/conversation/WelcomeMessage.js.map +1 -0
- package/lib/module/components/nav/Navigation.js +121 -0
- package/lib/module/components/nav/Navigation.js.map +1 -0
- package/lib/module/components/nav/constants.js +16 -0
- package/lib/module/components/nav/constants.js.map +1 -0
- package/lib/module/graphql/ApolloContainer.js +1 -2
- package/lib/module/graphql/ApolloContainer.js.map +1 -1
- package/lib/module/graphql/mutation.js +57 -23
- package/lib/module/graphql/mutation.js.map +1 -1
- package/lib/module/graphql/query.js +83 -52
- package/lib/module/graphql/query.js.map +1 -1
- package/lib/module/graphql/subscription.js +31 -28
- package/lib/module/graphql/subscription.js.map +1 -1
- package/lib/module/screen/conversation/Attachment.js +79 -14
- package/lib/module/screen/conversation/Attachment.js.map +1 -1
- package/lib/module/screen/conversation/ConversationDetail.js +311 -139
- package/lib/module/screen/conversation/ConversationDetail.js.map +1 -1
- package/lib/module/screen/conversation/Message.js +145 -82
- package/lib/module/screen/conversation/Message.js.map +1 -1
- package/lib/module/screen/faq/Faq.js +363 -0
- package/lib/module/screen/faq/Faq.js.map +1 -0
- package/lib/module/screen/greetings/Social.js +19 -9
- package/lib/module/screen/greetings/Social.js.map +1 -1
- package/lib/module/screen/greetings/Supporters.js +50 -46
- package/lib/module/screen/greetings/Supporters.js.map +1 -1
- package/lib/module/screen/home/Home.js +236 -0
- package/lib/module/screen/home/Home.js.map +1 -0
- package/lib/module/screen/messages/Messages.js +303 -0
- package/lib/module/screen/messages/Messages.js.map +1 -0
- package/lib/module/theme.js +122 -0
- package/lib/module/theme.js.map +1 -0
- package/lib/module/utils/messages.js +81 -0
- package/lib/module/utils/messages.js.map +1 -0
- package/lib/module/utils/onlineHours.js +118 -0
- package/lib/module/utils/onlineHours.js.map +1 -0
- package/lib/module/utils/upload.js +233 -0
- package/lib/module/utils/upload.js.map +1 -0
- package/lib/typescript/App.d.ts +2 -1
- package/lib/typescript/App.d.ts.map +1 -1
- package/lib/typescript/Widget.d.ts.map +1 -1
- package/lib/typescript/components/Avatar.d.ts +7 -1
- package/lib/typescript/components/Avatar.d.ts.map +1 -1
- package/lib/typescript/components/AvatarWithStatus.d.ts +11 -0
- package/lib/typescript/components/AvatarWithStatus.d.ts.map +1 -0
- package/lib/typescript/components/ConversationItem.d.ts +9 -0
- package/lib/typescript/components/ConversationItem.d.ts.map +1 -0
- package/lib/typescript/components/Icons.d.ts +33 -0
- package/lib/typescript/components/Icons.d.ts.map +1 -0
- package/lib/typescript/components/InputTools.d.ts.map +1 -1
- package/lib/typescript/components/MessengerShell.d.ts +4 -0
- package/lib/typescript/components/MessengerShell.d.ts.map +1 -0
- package/lib/typescript/components/conversation/DateSeparator.d.ts +6 -0
- package/lib/typescript/components/conversation/DateSeparator.d.ts.map +1 -0
- package/lib/typescript/components/conversation/EmojiPicker.d.ts +7 -0
- package/lib/typescript/components/conversation/EmojiPicker.d.ts.map +1 -0
- package/lib/typescript/components/conversation/PersistentMenu.d.ts +13 -0
- package/lib/typescript/components/conversation/PersistentMenu.d.ts.map +1 -0
- package/lib/typescript/components/conversation/TypingStatus.d.ts +4 -0
- package/lib/typescript/components/conversation/TypingStatus.d.ts.map +1 -0
- package/lib/typescript/components/conversation/WelcomeMessage.d.ts +6 -0
- package/lib/typescript/components/conversation/WelcomeMessage.d.ts.map +1 -0
- package/lib/typescript/components/nav/Navigation.d.ts +12 -0
- package/lib/typescript/components/nav/Navigation.d.ts.map +1 -0
- package/lib/typescript/components/nav/constants.d.ts +7 -0
- package/lib/typescript/components/nav/constants.d.ts.map +1 -0
- package/lib/typescript/graphql/ApolloContainer.d.ts.map +1 -1
- package/lib/typescript/graphql/mutation.d.ts +2 -1
- package/lib/typescript/graphql/mutation.d.ts.map +1 -1
- package/lib/typescript/graphql/query.d.ts +2 -3
- package/lib/typescript/graphql/query.d.ts.map +1 -1
- package/lib/typescript/graphql/subscription.d.ts +2 -1
- package/lib/typescript/graphql/subscription.d.ts.map +1 -1
- package/lib/typescript/screen/conversation/Attachment.d.ts.map +1 -1
- package/lib/typescript/screen/conversation/ConversationDetail.d.ts.map +1 -1
- package/lib/typescript/screen/conversation/Message.d.ts.map +1 -1
- package/lib/typescript/screen/faq/Faq.d.ts +4 -0
- package/lib/typescript/screen/faq/Faq.d.ts.map +1 -0
- package/lib/typescript/screen/greetings/Social.d.ts +1 -1
- package/lib/typescript/screen/greetings/Social.d.ts.map +1 -1
- package/lib/typescript/screen/greetings/Supporters.d.ts.map +1 -1
- package/lib/typescript/screen/home/Home.d.ts +4 -0
- package/lib/typescript/screen/home/Home.d.ts.map +1 -0
- package/lib/typescript/screen/messages/Messages.d.ts +4 -0
- package/lib/typescript/screen/messages/Messages.d.ts.map +1 -0
- package/lib/typescript/theme.d.ts +93 -0
- package/lib/typescript/theme.d.ts.map +1 -0
- package/lib/typescript/utils/messages.d.ts +27 -0
- package/lib/typescript/utils/messages.d.ts.map +1 -0
- package/lib/typescript/utils/onlineHours.d.ts +19 -0
- package/lib/typescript/utils/onlineHours.d.ts.map +1 -0
- package/lib/typescript/utils/upload.d.ts +32 -0
- package/lib/typescript/utils/upload.d.ts.map +1 -0
- package/package.json +25 -21
- package/src/App.tsx +48 -24
- package/src/Widget.tsx +214 -40
- package/src/components/Avatar.tsx +30 -30
- package/src/components/AvatarWithStatus.tsx +63 -0
- package/src/components/ConversationItem.tsx +199 -0
- package/src/components/Icons.tsx +532 -0
- package/src/components/InputTools.tsx +387 -48
- package/src/components/MessengerShell.tsx +58 -0
- package/src/components/conversation/DateSeparator.tsx +36 -0
- package/src/components/conversation/EmojiPicker.tsx +111 -0
- package/src/components/conversation/PersistentMenu.tsx +74 -0
- package/src/components/conversation/TypingStatus.tsx +112 -0
- package/src/components/conversation/WelcomeMessage.tsx +70 -0
- package/src/components/nav/Navigation.tsx +137 -0
- package/src/components/nav/constants.ts +16 -0
- package/src/graphql/ApolloContainer.tsx +0 -1
- package/src/graphql/mutation.ts +58 -22
- package/src/graphql/query.ts +83 -55
- package/src/graphql/subscription.ts +31 -27
- package/src/screen/conversation/Attachment.tsx +123 -33
- package/src/screen/conversation/ConversationDetail.tsx +367 -134
- package/src/screen/conversation/Message.tsx +160 -98
- package/src/screen/faq/Faq.tsx +425 -0
- package/src/screen/greetings/Social.tsx +18 -4
- package/src/screen/greetings/Supporters.tsx +56 -41
- package/src/screen/home/Home.tsx +276 -0
- package/src/screen/messages/Messages.tsx +331 -0
- package/src/theme.ts +104 -0
- package/src/utils/messages.ts +105 -0
- package/src/utils/onlineHours.ts +167 -0
- package/src/utils/upload.ts +326 -0
- package/lib/commonjs/components/FAB.js +0 -51
- package/lib/commonjs/components/FAB.js.map +0 -1
- package/lib/commonjs/screen/conversation/Conversations.js +0 -158
- package/lib/commonjs/screen/conversation/Conversations.js.map +0 -1
- package/lib/commonjs/screen/greetings/Greetings.js +0 -96
- package/lib/commonjs/screen/greetings/Greetings.js.map +0 -1
- package/lib/module/components/FAB.js +0 -42
- package/lib/module/components/FAB.js.map +0 -1
- package/lib/module/screen/conversation/Conversations.js +0 -147
- package/lib/module/screen/conversation/Conversations.js.map +0 -1
- package/lib/module/screen/greetings/Greetings.js +0 -85
- package/lib/module/screen/greetings/Greetings.js.map +0 -1
- package/lib/typescript/components/FAB.d.ts +0 -16
- package/lib/typescript/components/FAB.d.ts.map +0 -1
- package/lib/typescript/screen/conversation/Conversations.d.ts +0 -4
- package/lib/typescript/screen/conversation/Conversations.d.ts.map +0 -1
- package/lib/typescript/screen/greetings/Greetings.d.ts +0 -4
- package/lib/typescript/screen/greetings/Greetings.d.ts.map +0 -1
- package/src/components/FAB.tsx +0 -69
- package/src/screen/conversation/Conversations.tsx +0 -148
- package/src/screen/greetings/Greetings.tsx +0 -98
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Message list helpers ported from the web messenger SDK
|
|
2
|
+
// (frontline-widgets conversation-details.tsx): date keys, friendly date
|
|
3
|
+
// labels, and grouping messages by sender + a 5-minute time window so that only
|
|
4
|
+
// the last message of a run shows an avatar/timestamp and the first shows the
|
|
5
|
+
// sender name.
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
|
|
8
|
+
export const MESSAGE_GROUP_TIME_WINDOW = 5 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
export const getDateKey = (date: any): string =>
|
|
11
|
+
dayjs(date).format('YYYY-MM-DD');
|
|
12
|
+
|
|
13
|
+
export const formatMessageDate = (dateKey: string): string => {
|
|
14
|
+
const d = dayjs(dateKey);
|
|
15
|
+
if (d.isSame(dayjs(), 'day')) {
|
|
16
|
+
return 'Today';
|
|
17
|
+
}
|
|
18
|
+
if (d.isSame(dayjs().subtract(1, 'day'), 'day')) {
|
|
19
|
+
return 'Yesterday';
|
|
20
|
+
}
|
|
21
|
+
return d.format('MMM D, YYYY');
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isOutgoing = (m: any): boolean => Boolean(m?.customerId);
|
|
25
|
+
|
|
26
|
+
// Two messages belong to the same visual group when they are from the same
|
|
27
|
+
// side (customer vs operator), the same operator user, on the same day, and
|
|
28
|
+
// within the grouping time window.
|
|
29
|
+
const sameGroup = (a: any, b: any): boolean => {
|
|
30
|
+
if (isOutgoing(a) !== isOutgoing(b)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (!isOutgoing(a) && (a?.user?._id || '') !== (b?.user?._id || '')) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (getDateKey(a?.createdAt) !== getDateKey(b?.createdAt)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return (
|
|
40
|
+
Math.abs(
|
|
41
|
+
new Date(b?.createdAt).getTime() - new Date(a?.createdAt).getTime()
|
|
42
|
+
) <= MESSAGE_GROUP_TIME_WINDOW
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ChatRow =
|
|
47
|
+
| { type: 'welcome'; id: string; content?: string }
|
|
48
|
+
| { type: 'date'; id: string; label: string }
|
|
49
|
+
| {
|
|
50
|
+
type: 'message';
|
|
51
|
+
id: string;
|
|
52
|
+
item: any;
|
|
53
|
+
isFirstInGroup: boolean;
|
|
54
|
+
isLastInGroup: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build the chronological row list (welcome → date separators → grouped
|
|
59
|
+
* messages) for the chat screen.
|
|
60
|
+
*
|
|
61
|
+
* @param chronological messages oldest-first
|
|
62
|
+
* @param welcomeContent optional welcome bubble shown at the very top
|
|
63
|
+
*/
|
|
64
|
+
export const buildChatRows = (
|
|
65
|
+
chronological: any[],
|
|
66
|
+
welcomeContent?: string
|
|
67
|
+
): ChatRow[] => {
|
|
68
|
+
const rows: ChatRow[] = [];
|
|
69
|
+
|
|
70
|
+
// Hide internal notes; bot/welcome handled separately.
|
|
71
|
+
const visible = (chronological || []).filter((m) => !m?.internal);
|
|
72
|
+
|
|
73
|
+
let lastDateKey: string | null = null;
|
|
74
|
+
|
|
75
|
+
visible.forEach((m, i) => {
|
|
76
|
+
const dateKey = getDateKey(m?.createdAt);
|
|
77
|
+
if (dateKey !== lastDateKey) {
|
|
78
|
+
rows.push({
|
|
79
|
+
type: 'date',
|
|
80
|
+
id: `date-${dateKey}-${i}`,
|
|
81
|
+
label: formatMessageDate(dateKey),
|
|
82
|
+
});
|
|
83
|
+
lastDateKey = dateKey;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const prev = visible[i - 1];
|
|
87
|
+
const next = visible[i + 1];
|
|
88
|
+
const isFirstInGroup = !prev || !sameGroup(prev, m);
|
|
89
|
+
const isLastInGroup = !next || !sameGroup(m, next);
|
|
90
|
+
|
|
91
|
+
rows.push({
|
|
92
|
+
type: 'message',
|
|
93
|
+
id: m._id,
|
|
94
|
+
item: m,
|
|
95
|
+
isFirstInGroup,
|
|
96
|
+
isLastInGroup,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (welcomeContent) {
|
|
101
|
+
rows.unshift({ type: 'welcome', id: 'welcome', content: welcomeContent });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return rows;
|
|
105
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
type OnlineHour = {
|
|
2
|
+
day: string;
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type FormatOnlineHoursOptions = {
|
|
8
|
+
onlineHours?: OnlineHour[];
|
|
9
|
+
timezone?: string;
|
|
10
|
+
showTimezone?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const AVAILABILITY_MESSAGE =
|
|
14
|
+
"We're available between 9.00 am and 6.00 pm (GMT +8). We'll get back to you as soon as possible.";
|
|
15
|
+
|
|
16
|
+
const parseTime = (timeString: string): { hour: number; minute: number } => {
|
|
17
|
+
const normalized = timeString.toLowerCase().trim();
|
|
18
|
+
const colonIndex = normalized.indexOf(':');
|
|
19
|
+
|
|
20
|
+
if (colonIndex === -1) {
|
|
21
|
+
throw new Error(`Invalid time format: ${timeString}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let hour = Number.parseInt(normalized.substring(0, colonIndex), 10);
|
|
25
|
+
const minute = Number.parseInt(
|
|
26
|
+
normalized.substring(colonIndex + 1, colonIndex + 3),
|
|
27
|
+
10
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const isPM = normalized.includes('pm');
|
|
31
|
+
const isAM = normalized.includes('am');
|
|
32
|
+
|
|
33
|
+
if (isPM && hour !== 12) {
|
|
34
|
+
hour += 12;
|
|
35
|
+
} else if (isAM && hour === 12) {
|
|
36
|
+
hour = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { hour, minute };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const formatTimeTo12Hour = (timeString: string): string => {
|
|
43
|
+
const { hour, minute } = parseTime(timeString);
|
|
44
|
+
const hour12 = hour > 12 ? hour - 12 : hour || 12;
|
|
45
|
+
const period = hour < 12 ? 'am' : 'pm';
|
|
46
|
+
const minuteStr = minute.toString().padStart(2, '0');
|
|
47
|
+
|
|
48
|
+
return `${hour12}.${minuteStr} ${period}`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const formatTimeZoneLabel = (timezone: string): string => {
|
|
52
|
+
try {
|
|
53
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
54
|
+
timeZone: timezone,
|
|
55
|
+
timeZoneName: 'shortOffset',
|
|
56
|
+
}).formatToParts(new Date());
|
|
57
|
+
const value = parts.find((part) => part.type === 'timeZoneName')?.value;
|
|
58
|
+
|
|
59
|
+
return value || timezone;
|
|
60
|
+
} catch {
|
|
61
|
+
return timezone;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const formatTimezone = (timezone?: string, showTimezone?: boolean): string => {
|
|
66
|
+
if (!showTimezone || !timezone) {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const timezoneLabel = formatTimeZoneLabel(timezone);
|
|
71
|
+
const gmtMatch = timezoneLabel.match(/GMT([+-])(\d{1,2}):?(\d{0,2})/);
|
|
72
|
+
|
|
73
|
+
if (!gmtMatch) {
|
|
74
|
+
return ` (${timezoneLabel})`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const signPart = gmtMatch[1];
|
|
78
|
+
const hourPart = gmtMatch[2];
|
|
79
|
+
|
|
80
|
+
if (!signPart || !hourPart) {
|
|
81
|
+
return ` (${timezoneLabel})`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const sign = signPart === '+' ? '+' : '-';
|
|
85
|
+
const hours = Number.parseInt(hourPart, 10);
|
|
86
|
+
const minutes = gmtMatch[3] ? Number.parseInt(gmtMatch[3], 10) : 0;
|
|
87
|
+
|
|
88
|
+
return minutes === 0
|
|
89
|
+
? ` (GMT ${sign}${hours})`
|
|
90
|
+
: ` (GMT ${sign}${hours}:${minutes.toString().padStart(2, '0')})`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const formatOnlineHours = ({
|
|
94
|
+
onlineHours,
|
|
95
|
+
timezone,
|
|
96
|
+
showTimezone,
|
|
97
|
+
}: FormatOnlineHoursOptions): {
|
|
98
|
+
workHours: string;
|
|
99
|
+
formattedTimeZone: string;
|
|
100
|
+
onlineDays: string;
|
|
101
|
+
} | null => {
|
|
102
|
+
const firstHour = onlineHours?.[0];
|
|
103
|
+
|
|
104
|
+
if (!firstHour?.from || !firstHour?.to) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
return {
|
|
110
|
+
workHours: `${formatTimeTo12Hour(
|
|
111
|
+
firstHour.from
|
|
112
|
+
)} and ${formatTimeTo12Hour(firstHour.to)}`,
|
|
113
|
+
formattedTimeZone: formatTimezone(timezone, showTimezone).trim(),
|
|
114
|
+
onlineDays: onlineHours?.map((item) => item.day).join(', ') || '',
|
|
115
|
+
};
|
|
116
|
+
} catch {
|
|
117
|
+
return {
|
|
118
|
+
workHours: `${firstHour.from} and ${firstHour.to}`,
|
|
119
|
+
formattedTimeZone: formatTimezone(timezone, showTimezone).trim(),
|
|
120
|
+
onlineDays: onlineHours?.map((item) => item.day).join(', ') || '',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const formatOnlineHoursShort = ({
|
|
126
|
+
onlineHours,
|
|
127
|
+
timezone,
|
|
128
|
+
showTimezone,
|
|
129
|
+
}: FormatOnlineHoursOptions): string => {
|
|
130
|
+
const firstHour = onlineHours?.[0];
|
|
131
|
+
|
|
132
|
+
if (!firstHour?.from || !firstHour?.to) {
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const from = formatTimeTo12Hour(firstHour.from)
|
|
138
|
+
.replace(/\s+/g, '')
|
|
139
|
+
.replace('.00', '');
|
|
140
|
+
const to = formatTimeTo12Hour(firstHour.to)
|
|
141
|
+
.replace(/\s+/g, '')
|
|
142
|
+
.replace('.00', '');
|
|
143
|
+
|
|
144
|
+
let timezoneSuffix = '';
|
|
145
|
+
|
|
146
|
+
if (showTimezone && timezone) {
|
|
147
|
+
const timezoneLabel = formatTimeZoneLabel(timezone);
|
|
148
|
+
const gmtMatch = timezoneLabel.match(
|
|
149
|
+
/(?:\()?GMT([+-])(\d{1,2}):?(\d{0,2})(?:\))?/
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (gmtMatch?.[1] && gmtMatch[2]) {
|
|
153
|
+
const sign = gmtMatch[1];
|
|
154
|
+
const hours = Number.parseInt(gmtMatch[2], 10);
|
|
155
|
+
const minutes = gmtMatch[3] ? Number.parseInt(gmtMatch[3], 10) : 0;
|
|
156
|
+
const minutePart =
|
|
157
|
+
minutes === 0 ? '' : `:${minutes.toString().padStart(2, '0')}`;
|
|
158
|
+
|
|
159
|
+
timezoneSuffix = ` (GMT${sign}${hours}${minutePart})`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return `${from}-${to}${timezoneSuffix}`;
|
|
164
|
+
} catch {
|
|
165
|
+
return `${firstHour.from}-${firstHour.to}`;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// File-upload pipeline for the chat composer.
|
|
2
|
+
//
|
|
3
|
+
// Supports BOTH picker libraries so the SDK works in either setup:
|
|
4
|
+
// - Expo (managed / Expo Go) -> `expo-image-picker`
|
|
5
|
+
// - bare React Native -> `react-native-image-picker`
|
|
6
|
+
// Both are optional and resolved lazily, so the composer degrades gracefully
|
|
7
|
+
// (the attach button hides) when neither is installed. The uploaded file key is
|
|
8
|
+
// returned and sent as a message attachment via the existing
|
|
9
|
+
// `widgetsInsertMessage` mutation (which already accepts attachments).
|
|
10
|
+
//
|
|
11
|
+
// NOTE: the upload endpoint mirrors the web uploader:
|
|
12
|
+
// `POST /upload-file?kind=main&maxHeight=0&maxWidth=0`.
|
|
13
|
+
|
|
14
|
+
export type PickedFile = {
|
|
15
|
+
uri: string;
|
|
16
|
+
name: string;
|
|
17
|
+
type: string;
|
|
18
|
+
size?: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type UploadedAttachment = {
|
|
22
|
+
url: string;
|
|
23
|
+
name: string;
|
|
24
|
+
type: string;
|
|
25
|
+
size?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type UploadResponseType = 'text' | 'json';
|
|
29
|
+
|
|
30
|
+
export type UploadOptions = {
|
|
31
|
+
url?: string;
|
|
32
|
+
kind?: string;
|
|
33
|
+
userId?: string;
|
|
34
|
+
responseType?: UploadResponseType;
|
|
35
|
+
maxHeight?: number;
|
|
36
|
+
maxWidth?: number;
|
|
37
|
+
extraFormData?: Array<{ key: string; value: string }>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const ALLOWED_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png'];
|
|
41
|
+
const ALLOWED_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png'];
|
|
42
|
+
|
|
43
|
+
export const unsupportedImageUploadMessage =
|
|
44
|
+
'Only PNG and JPG images can be uploaded.';
|
|
45
|
+
|
|
46
|
+
const getCleanPath = (value?: string | null): string => {
|
|
47
|
+
const withoutQuery = (value || '').split('?')[0] || '';
|
|
48
|
+
|
|
49
|
+
return withoutQuery.split('#')[0] || '';
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getExtension = (value?: string | null): string => {
|
|
53
|
+
const match = getCleanPath(value).match(/\.([a-zA-Z0-9]+)$/);
|
|
54
|
+
|
|
55
|
+
return match && match[1] ? match[1].toLowerCase() : '';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const normalizeMimeType = (type?: string | null): string => {
|
|
59
|
+
if (!type) {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return type === 'image/jpg' ? 'image/jpeg' : type.toLowerCase();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getMimeTypeFromExtension = (extension: string): string => {
|
|
67
|
+
if (extension === 'jpg' || extension === 'jpeg') {
|
|
68
|
+
return 'image/jpeg';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (extension === 'png') {
|
|
72
|
+
return 'image/png';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return '';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getFileNameFromUri = (uri?: string | null): string => {
|
|
79
|
+
const fileName = getCleanPath(uri).split('/').pop() || '';
|
|
80
|
+
|
|
81
|
+
return getExtension(fileName) ? fileName : '';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const getImageFileName = (
|
|
85
|
+
fileName?: string | null,
|
|
86
|
+
uri?: string | null,
|
|
87
|
+
type?: string | null
|
|
88
|
+
): string => {
|
|
89
|
+
if (fileName && getExtension(fileName)) {
|
|
90
|
+
return fileName;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const uriFileName = getFileNameFromUri(uri);
|
|
94
|
+
if (uriFileName) {
|
|
95
|
+
return uriFileName;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const normalizedType = normalizeMimeType(type);
|
|
99
|
+
let extension = 'unsupported';
|
|
100
|
+
|
|
101
|
+
if (normalizedType === 'image/png') {
|
|
102
|
+
extension = 'png';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (normalizedType === 'image/jpeg') {
|
|
106
|
+
extension = 'jpg';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return `image-${Date.now()}.${extension}`;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getImageMimeType = (
|
|
113
|
+
type?: string | null,
|
|
114
|
+
fileName?: string | null,
|
|
115
|
+
uri?: string | null
|
|
116
|
+
): string => {
|
|
117
|
+
const normalizedType = normalizeMimeType(type);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
normalizedType ||
|
|
121
|
+
getMimeTypeFromExtension(getExtension(fileName)) ||
|
|
122
|
+
getMimeTypeFromExtension(getExtension(uri))
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const isAllowedImageFile = (file: PickedFile): boolean => {
|
|
127
|
+
const type = normalizeMimeType(file.type);
|
|
128
|
+
|
|
129
|
+
if (type) {
|
|
130
|
+
return ALLOWED_IMAGE_MIME_TYPES.includes(type);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const extension = getExtension(file.name) || getExtension(file.uri);
|
|
134
|
+
|
|
135
|
+
return ALLOWED_IMAGE_EXTENSIONS.includes(extension);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// NOTE: each require must be a LITERAL string inside try/catch so Metro treats
|
|
139
|
+
// it as an OPTIONAL dependency — it is bundled when installed and skipped
|
|
140
|
+
// (without failing the build) when absent. A variable require would never be
|
|
141
|
+
// bundled. Expo is preferred when present (works in Expo Go without a rebuild).
|
|
142
|
+
const getExpoPicker = (): any | null => {
|
|
143
|
+
try {
|
|
144
|
+
return require('expo-image-picker');
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const getRNPicker = (): any | null => {
|
|
151
|
+
try {
|
|
152
|
+
return require('react-native-image-picker');
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const isImagePickerAvailable = (): boolean =>
|
|
159
|
+
!!(
|
|
160
|
+
getExpoPicker()?.launchImageLibraryAsync ||
|
|
161
|
+
getRNPicker()?.launchImageLibrary
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
export const pickImage = async (): Promise<PickedFile | null> => {
|
|
165
|
+
const expo = getExpoPicker();
|
|
166
|
+
if (expo?.launchImageLibraryAsync) {
|
|
167
|
+
if (expo.requestMediaLibraryPermissionsAsync) {
|
|
168
|
+
const perm = await expo.requestMediaLibraryPermissionsAsync();
|
|
169
|
+
if (perm && perm.granted === false) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// `MediaType` is a TS-only type in expo-image-picker v16+; the runtime value
|
|
174
|
+
// is the string-array form. Using it avoids the deprecated
|
|
175
|
+
// `MediaTypeOptions` (and its warning).
|
|
176
|
+
const result = await expo.launchImageLibraryAsync({
|
|
177
|
+
mediaTypes: ['images'],
|
|
178
|
+
preferredAssetRepresentationMode:
|
|
179
|
+
expo.UIImagePickerPreferredAssetRepresentationMode?.Compatible ||
|
|
180
|
+
'compatible',
|
|
181
|
+
quality: 0.8,
|
|
182
|
+
});
|
|
183
|
+
if (result?.canceled || !result?.assets || result.assets.length === 0) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const asset = result.assets[0];
|
|
187
|
+
const name = getImageFileName(asset.fileName, asset.uri, asset.mimeType);
|
|
188
|
+
return {
|
|
189
|
+
uri: asset.uri,
|
|
190
|
+
name,
|
|
191
|
+
type: getImageMimeType(asset.mimeType, name, asset.uri),
|
|
192
|
+
size: asset.fileSize,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const rn = getRNPicker();
|
|
197
|
+
if (rn?.launchImageLibrary) {
|
|
198
|
+
const result = await rn.launchImageLibrary({
|
|
199
|
+
mediaType: 'photo',
|
|
200
|
+
selectionLimit: 1,
|
|
201
|
+
assetRepresentationMode: 'compatible',
|
|
202
|
+
quality: 0.8,
|
|
203
|
+
});
|
|
204
|
+
if (result?.didCancel || !result?.assets || result.assets.length === 0) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const asset = result.assets[0];
|
|
208
|
+
const name = getImageFileName(asset.fileName, asset.uri, asset.type);
|
|
209
|
+
return {
|
|
210
|
+
uri: asset.uri,
|
|
211
|
+
name,
|
|
212
|
+
type: getImageMimeType(asset.type, name, asset.uri),
|
|
213
|
+
size: asset.fileSize,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const getFileType = (file: PickedFile): string => {
|
|
221
|
+
return getImageMimeType(file.type, file.name, file.uri);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const normalizeUploadResponse = (
|
|
225
|
+
response: unknown,
|
|
226
|
+
responseType: UploadResponseType
|
|
227
|
+
): string => {
|
|
228
|
+
if (responseType === 'text') {
|
|
229
|
+
return String(response).trim();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (typeof response === 'string') {
|
|
233
|
+
return response.trim();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (response && typeof response === 'object') {
|
|
237
|
+
const body = response as Record<string, any>;
|
|
238
|
+
return String(
|
|
239
|
+
body.url || body.key || body.name || body.response || body.file || ''
|
|
240
|
+
).trim();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return '';
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// erxes gateway upload endpoint. erxes core serves file routes at the gateway
|
|
247
|
+
// root: `GET /read-file` (download, see getAttachmentUrl) and `POST
|
|
248
|
+
// /upload-file` (upload), which returns the stored file key as plain text.
|
|
249
|
+
export const getUploadUrl = (
|
|
250
|
+
subDomain: string,
|
|
251
|
+
options: UploadOptions = {}
|
|
252
|
+
): string => {
|
|
253
|
+
const {
|
|
254
|
+
url = `https://${subDomain}/gateway/upload-file`,
|
|
255
|
+
kind = 'main',
|
|
256
|
+
maxHeight = 0,
|
|
257
|
+
maxWidth = 0,
|
|
258
|
+
} = options;
|
|
259
|
+
|
|
260
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
261
|
+
|
|
262
|
+
return `${url}${separator}kind=${encodeURIComponent(
|
|
263
|
+
kind
|
|
264
|
+
)}&maxHeight=${maxHeight}&maxWidth=${maxWidth}`;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export const uploadFile = async (
|
|
268
|
+
file: PickedFile,
|
|
269
|
+
subDomain: string,
|
|
270
|
+
options: UploadOptions = {}
|
|
271
|
+
): Promise<UploadedAttachment> => {
|
|
272
|
+
const type = getFileType(file);
|
|
273
|
+
const uploadFileInfo = {
|
|
274
|
+
...file,
|
|
275
|
+
name: getImageFileName(file.name, file.uri, type),
|
|
276
|
+
type,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
if (!isAllowedImageFile(uploadFileInfo)) {
|
|
280
|
+
throw new Error(unsupportedImageUploadMessage);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const form = new FormData();
|
|
284
|
+
form.append('file', {
|
|
285
|
+
uri: uploadFileInfo.uri,
|
|
286
|
+
name: uploadFileInfo.name,
|
|
287
|
+
type: uploadFileInfo.type,
|
|
288
|
+
} as any);
|
|
289
|
+
|
|
290
|
+
if (options.extraFormData) {
|
|
291
|
+
options.extraFormData.forEach(({ key, value }) => {
|
|
292
|
+
form.append(key, value);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const responseType = options.responseType || 'text';
|
|
297
|
+
const response = await fetch(getUploadUrl(subDomain, options), {
|
|
298
|
+
method: 'POST',
|
|
299
|
+
body: form,
|
|
300
|
+
credentials: 'include',
|
|
301
|
+
...(options.userId ? { headers: { userId: options.userId } } : {}),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const responseBody = await response[responseType]();
|
|
305
|
+
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
const detail =
|
|
308
|
+
typeof responseBody === 'string'
|
|
309
|
+
? responseBody.slice(0, 200)
|
|
310
|
+
: JSON.stringify(responseBody).slice(0, 200);
|
|
311
|
+
throw new Error(`Upload failed (${response.status}) ${detail}`.trim());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const key = normalizeUploadResponse(responseBody, responseType);
|
|
315
|
+
|
|
316
|
+
if (!key) {
|
|
317
|
+
throw new Error('Upload failed: empty file key');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
url: key,
|
|
322
|
+
name: uploadFileInfo.name,
|
|
323
|
+
type: uploadFileInfo.type,
|
|
324
|
+
size: uploadFileInfo.size,
|
|
325
|
+
};
|
|
326
|
+
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _react = _interopRequireDefault(require("react"));
|
|
8
|
-
var _reactNative = require("react-native");
|
|
9
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
|
-
/* eslint-disable react-native/no-inline-styles */
|
|
11
|
-
|
|
12
|
-
const FAB = props => {
|
|
13
|
-
const {
|
|
14
|
-
icon,
|
|
15
|
-
onPress,
|
|
16
|
-
backgroundColor = '#2F1F69',
|
|
17
|
-
style,
|
|
18
|
-
bottom = 20
|
|
19
|
-
} = props;
|
|
20
|
-
return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, {
|
|
21
|
-
onPress: onPress,
|
|
22
|
-
style: [styles.default, {
|
|
23
|
-
backgroundColor: backgroundColor ? backgroundColor : '#2F1F69',
|
|
24
|
-
bottom: bottom
|
|
25
|
-
}, style]
|
|
26
|
-
}, icon);
|
|
27
|
-
};
|
|
28
|
-
const styles = _reactNative.StyleSheet.create({
|
|
29
|
-
default: {
|
|
30
|
-
position: 'absolute',
|
|
31
|
-
right: 20,
|
|
32
|
-
bottom: 20,
|
|
33
|
-
height: 60,
|
|
34
|
-
width: 60,
|
|
35
|
-
borderRadius: 50,
|
|
36
|
-
justifyContent: 'center',
|
|
37
|
-
alignItems: 'center',
|
|
38
|
-
elevation: 3,
|
|
39
|
-
zIndex: 3,
|
|
40
|
-
shadowColor: '#2F1F69',
|
|
41
|
-
shadowOffset: {
|
|
42
|
-
width: 0,
|
|
43
|
-
height: 0
|
|
44
|
-
},
|
|
45
|
-
shadowOpacity: 0.5,
|
|
46
|
-
shadowRadius: 3
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
var _default = FAB;
|
|
50
|
-
exports.default = _default;
|
|
51
|
-
//# sourceMappingURL=FAB.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireDefault","require","_reactNative","obj","__esModule","default","FAB","props","icon","onPress","backgroundColor","style","bottom","createElement","TouchableOpacity","styles","StyleSheet","create","position","right","height","width","borderRadius","justifyContent","alignItems","elevation","zIndex","shadowColor","shadowOffset","shadowOpacity","shadowRadius","_default","exports"],"sourceRoot":"../../../src","sources":["components/FAB.tsx"],"mappings":";;;;;;AACA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AAKsB,SAAAD,uBAAAG,GAAA,WAAAA,GAAA,IAAAA,GAAA,CAAAC,UAAA,GAAAD,GAAA,KAAAE,OAAA,EAAAF,GAAA;AAPtB;;AAqBA,MAAMG,GAAG,GAAIC,KAAY,IAAK;EAC5B,MAAM;IACJC,IAAI;IACJC,OAAO;IACPC,eAAe,GAAG,SAAS;IAC3BC,KAAK;IACLC,MAAM,GAAG;EACX,CAAC,GAAGL,KAAK;EACT,oBACER,MAAA,CAAAM,OAAA,CAAAQ,aAAA,CAACX,YAAA,CAAAY,gBAAgB;IACfL,OAAO,EAAEA,OAAQ;IACjBE,KAAK,EAAE,CACLI,MAAM,CAACV,OAAO,EACd;MACEK,eAAe,EAAEA,eAAe,GAAGA,eAAe,GAAG,SAAS;MAC9DE,MAAM,EAAEA;IACV,CAAC,EACDD,KAAK;EACL,GAEDH,IACe,CAAC;AAEvB,CAAC;AAED,MAAMO,MAAM,GAAGC,uBAAU,CAACC,MAAM,CAAC;EAC/BZ,OAAO,EAAE;IACPa,QAAQ,EAAE,UAAU;IACpBC,KAAK,EAAE,EAAE;IACTP,MAAM,EAAE,EAAE;IACVQ,MAAM,EAAE,EAAE;IACVC,KAAK,EAAE,EAAE;IACTC,YAAY,EAAE,EAAE;IAChBC,cAAc,EAAE,QAAQ;IACxBC,UAAU,EAAE,QAAQ;IACpBC,SAAS,EAAE,CAAC;IACZC,MAAM,EAAE,CAAC;IACTC,WAAW,EAAE,SAAS;IACtBC,YAAY,EAAE;MACZP,KAAK,EAAE,CAAC;MACRD,MAAM,EAAE;IACV,CAAC;IACDS,aAAa,EAAE,GAAG;IAClBC,YAAY,EAAE;EAChB;AACF,CAAC,CAAC;AAAC,IAAAC,QAAA,GAEYzB,GAAG;AAAA0B,OAAA,CAAA3B,OAAA,GAAA0B,QAAA"}
|