@droppii-org/chat-sdk 0.7.5 → 0.7.7
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/dist/components/conversation/DeskBackgroundSyncStatus.d.ts +4 -0
- package/dist/components/conversation/DeskBackgroundSyncStatus.d.ts.map +1 -0
- package/dist/components/conversation/DeskBackgroundSyncStatus.js +17 -0
- package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
- package/dist/components/conversation/DeskConversationList.js +13 -3
- package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationMessageList.js +3 -6
- package/dist/components/conversation-detail/PinnedMessagesPanel.d.ts.map +1 -1
- package/dist/components/conversation-detail/PinnedMessagesPanel.js +1 -1
- package/dist/components/conversation-detail/bot/BotMenuSheet.d.ts.map +1 -1
- package/dist/components/conversation-detail/bot/BotMenuSheet.js +1 -1
- package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.d.ts +10 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.js +15 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.d.ts.map +1 -1
- package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.js +39 -28
- package/dist/components/conversation-inbox/contact-search/constants.js +2 -2
- package/dist/components/message/MessageList.d.ts.map +1 -1
- package/dist/components/message/MessageList.js +8 -6
- package/dist/hooks/conversation/useResolveConversation.d.ts.map +1 -1
- package/dist/hooks/conversation/useResolveConversation.js +2 -5
- package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
- package/dist/hooks/global/useGlobalEvent.js +2 -0
- package/dist/hooks/init/useCanLoadChatData.d.ts +7 -0
- package/dist/hooks/init/useCanLoadChatData.d.ts.map +1 -0
- package/dist/hooks/init/useCanLoadChatData.js +15 -0
- package/dist/hooks/message/useCanPinMessage.d.ts +2 -0
- package/dist/hooks/message/useCanPinMessage.d.ts.map +1 -0
- package/dist/hooks/message/useCanPinMessage.js +29 -0
- package/dist/hooks/message/useMessage.d.ts.map +1 -1
- package/dist/hooks/message/useMessage.js +3 -5
- package/dist/hooks/message/useUnreadMessageIndicator.d.ts.map +1 -1
- package/dist/hooks/message/useUnreadMessageIndicator.js +3 -5
- package/dist/hooks/search/useSearchPublicGroups.d.ts +1 -0
- package/dist/hooks/search/useSearchPublicGroups.d.ts.map +1 -1
- package/dist/hooks/search/useSearchPublicGroups.js +6 -0
- package/dist/hooks/session/useAgentSupportThreadJoin.d.ts +1 -0
- package/dist/hooks/session/useAgentSupportThreadJoin.d.ts.map +1 -1
- package/dist/hooks/session/useAgentSupportThreadJoin.js +13 -2
- package/dist/hooks/session/useSessionLatestPreview.d.ts +4 -1
- package/dist/hooks/session/useSessionLatestPreview.d.ts.map +1 -1
- package/dist/hooks/session/useSessionLatestPreview.js +4 -1
- package/dist/locales/vi/boot.json +3 -2
- package/dist/locales/vi/common.json +1 -0
- package/dist/screens/conversation-detail/index.d.ts.map +1 -1
- package/dist/screens/conversation-detail/index.js +3 -1
- package/dist/screens/desk-message/DeskWorkspace.d.ts +3 -1
- package/dist/screens/desk-message/DeskWorkspace.d.ts.map +1 -1
- package/dist/screens/desk-message/DeskWorkspace.js +4 -4
- package/dist/screens/desk-message/index.d.ts +2 -0
- package/dist/screens/desk-message/index.d.ts.map +1 -1
- package/dist/screens/desk-message/index.js +26 -8
- package/dist/store/boot.d.ts +15 -0
- package/dist/store/boot.d.ts.map +1 -1
- package/dist/store/boot.js +20 -2
- package/dist/styles/global.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAgentSupportThreadJoin.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useAgentSupportThreadJoin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useAgentSupportThreadJoin.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useAgentSupportThreadJoin.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,8BAA8B,CAAC;AAatC,eAAO,MAAM,yBAAyB,GAAI,iBAAiB,MAAM,YACF,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GACpC,eAAe,gBAAgB,GAAG,IAAI,EACtC,aAAa,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC;;;;;CA8E1D,CAAC"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback, useEffect, useState } from "react";
|
|
3
3
|
import { message } from "antd";
|
|
4
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
5
|
import { useTranslation } from "react-i18next";
|
|
5
6
|
import { CbEvents, } from "@droppii-org/wasm-client-sdk";
|
|
6
7
|
import { DChatSDK } from "../../constants/sdk";
|
|
8
|
+
import { QUERY_KEYS } from "../../services/query";
|
|
7
9
|
import useAuthStore from "../../store/auth";
|
|
8
10
|
import { PeerType } from "../../types/conversation";
|
|
9
11
|
import { isSupportSessionAgentView } from "../../utils/conversation";
|
|
@@ -12,6 +14,7 @@ import { useIsJoinedGroup } from "./useIsJoinedGroup";
|
|
|
12
14
|
// check right after still says "not a member". Joins made in this page
|
|
13
15
|
// session are trusted instead — kept outside React so a remount keeps them.
|
|
14
16
|
const joinedConversationIds = new Set();
|
|
17
|
+
export const isSupportThreadJoinedHere = (conversationId) => !!conversationId && joinedConversationIds.has(conversationId);
|
|
15
18
|
/**
|
|
16
19
|
* CRM's join gate for the thread detail: an agent viewing a support thread
|
|
17
20
|
* they aren't a member of can't send until they join. Opt-in — only a host
|
|
@@ -19,6 +22,7 @@ const joinedConversationIds = new Set();
|
|
|
19
22
|
*/
|
|
20
23
|
export const useAgentSupportThreadJoin = (conversation, joinThread) => {
|
|
21
24
|
const { t } = useTranslation();
|
|
25
|
+
const queryClient = useQueryClient();
|
|
22
26
|
const userID = useAuthStore((state) => state.userID);
|
|
23
27
|
const isCrm = useAuthStore((state) => state.isCrm);
|
|
24
28
|
const isAgentSupportThread = !!joinThread &&
|
|
@@ -68,8 +72,15 @@ export const useAgentSupportThreadJoin = (conversation, joinThread) => {
|
|
|
68
72
|
joinedConversationIds.add(conversationId);
|
|
69
73
|
setJoinCount((count) => count + 1);
|
|
70
74
|
message.success(t("join_group_success"));
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
// Re-renders other readers of this membership (pin rights); a join on a
|
|
76
|
+
// cached group syncs as an update, so no join event would refetch it.
|
|
77
|
+
if (groupId) {
|
|
78
|
+
const queryKey = [QUERY_KEYS.IS_JOINED_GROUP, groupId];
|
|
79
|
+
await queryClient.cancelQueries({ queryKey });
|
|
80
|
+
queryClient.setQueryData(queryKey, true);
|
|
81
|
+
}
|
|
82
|
+
}, [conversationId, groupId, joinThread, queryClient, t]);
|
|
83
|
+
const isJoinedHere = isSupportThreadJoinedHere(conversationId);
|
|
73
84
|
const isGated = !!groupId && !isJoinedHere;
|
|
74
85
|
return {
|
|
75
86
|
isCheckingMembership: isGated && isJoined === null && !isError,
|
|
@@ -7,5 +7,8 @@ export interface ConversationPreviewSource {
|
|
|
7
7
|
conversationId: string;
|
|
8
8
|
conversation?: ConversationItem;
|
|
9
9
|
}
|
|
10
|
-
export declare const useSessionLatestPreview: (sessions: ConversationPreviewSource[]) =>
|
|
10
|
+
export declare const useSessionLatestPreview: (sessions: ConversationPreviewSource[]) => {
|
|
11
|
+
previews: Map<string, LatestPreview>;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
};
|
|
11
14
|
//# sourceMappingURL=useSessionLatestPreview.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSessionLatestPreview.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useSessionLatestPreview.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAyB,MAAM,8BAA8B,CAAC;AASvF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAKD,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAkBD,eAAO,MAAM,uBAAuB,GAClC,UAAU,yBAAyB,EAAE
|
|
1
|
+
{"version":3,"file":"useSessionLatestPreview.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useSessionLatestPreview.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAyB,MAAM,8BAA8B,CAAC;AASvF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAKD,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAkBD,eAAO,MAAM,uBAAuB,GAClC,UAAU,yBAAyB,EAAE;;;CA8EtC,CAAC"}
|
|
@@ -54,7 +54,9 @@ export const useSessionLatestPreview = (sessions) => {
|
|
|
54
54
|
emitter.off("GROUP_MEMBERS_CHANGED", groupMembersChangedHandler);
|
|
55
55
|
};
|
|
56
56
|
}, [sessions, queryClient]);
|
|
57
|
-
|
|
57
|
+
// A preview is loading only while its query fetches for the first time.
|
|
58
|
+
const isLoading = results.some((result) => result.isLoading);
|
|
59
|
+
const previews = useMemo(() => {
|
|
58
60
|
const map = new Map();
|
|
59
61
|
sessions.forEach((session, index) => {
|
|
60
62
|
var _a, _b, _c;
|
|
@@ -77,4 +79,5 @@ export const useSessionLatestPreview = (sessions) => {
|
|
|
77
79
|
});
|
|
78
80
|
return map;
|
|
79
81
|
}, [sessions, results]);
|
|
82
|
+
return { previews, isLoading };
|
|
80
83
|
};
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
"sync": "Đồng bộ tin nhắn"
|
|
13
13
|
},
|
|
14
14
|
"slow": "Mạng đang chậm hơn bình thường. Livechat vẫn đang tải, bạn không cần làm gì thêm.",
|
|
15
|
-
"error_hint": "Kiểm tra kết nối mạng rồi bấm Thử lại. Nếu vẫn lỗi, báo bộ phận kỹ thuật: dừng ở bước {{step}}."
|
|
15
|
+
"error_hint": "Kiểm tra kết nối mạng rồi bấm Thử lại. Nếu vẫn lỗi, báo bộ phận kỹ thuật: dừng ở bước {{step}}.",
|
|
16
|
+
"background_sync": "Đang đồng bộ các cuộc trò chuyện còn lại"
|
|
16
17
|
},
|
|
17
18
|
"inbox": {
|
|
18
19
|
"title": "Đang tải tin nhắn",
|
|
19
20
|
"engine": "Đang chuẩn bị hộp thư…",
|
|
20
21
|
"connect": "Đang kết nối…",
|
|
21
|
-
"sync": "
|
|
22
|
+
"sync": "Vui lòng chờ trong giây lát…",
|
|
22
23
|
"slow": "Lần đầu mở có thể mất một chút thời gian. Những lần sau sẽ nhanh hơn.",
|
|
23
24
|
"error_title": "Chưa tải được tin nhắn",
|
|
24
25
|
"error_hint": "Kiểm tra kết nối mạng rồi thử lại."
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"context_menu_view_original": "Xem tin nhắn gốc",
|
|
75
75
|
"pinned_panel_title": "Tin nhắn đã ghim",
|
|
76
76
|
"pinned_panel_hint": "Nhấn giữ tin nhắn để chọn \"Bỏ ghim\" hoặc \"Xem tin nhắn gốc\"",
|
|
77
|
+
"pinned_panel_hint_view_only": "Nhấn giữ tin nhắn để chọn \"Xem tin nhắn gốc\"",
|
|
77
78
|
"pin_message_success": "Đã ghim tin nhắn",
|
|
78
79
|
"pin_message_failed": "Ghim tin nhắn thất bại",
|
|
79
80
|
"unpin_message_success": "Đã bỏ ghim tin nhắn",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAYrF,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,6CAA6C,CAAC;AAQrD,MAAM,WAAW,4BACf,SAAQ,4BAA4B,EAClC,4BAA4B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAED,QAAA,MAAM,uBAAuB,GAAI,8IAc9B,4BAA4B,mDA6Q9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -28,6 +28,7 @@ import { ConversationMessageList, ConversationDetailHeader, ConversationDetailIn
|
|
|
28
28
|
import TypingIndicatorBar from "../../components/conversation-detail/TypingIndicatorBar";
|
|
29
29
|
import { BotButtonMessage, BotInactiveNotice, BotMenuSheet, BotStartButton, BotSuggest, CsSessionConfirmModal, } from "../../components/conversation-detail/bot";
|
|
30
30
|
import { useBotFlow } from "../../hooks/conversation/useBotFlow";
|
|
31
|
+
import { useCanPinMessage } from "../../hooks/message/useCanPinMessage";
|
|
31
32
|
import { BOT_TYPE } from "../../constants/botFlow";
|
|
32
33
|
import { useResolveConversation, } from "../../hooks/conversation/useResolveConversation";
|
|
33
34
|
import { nextConsultantReturnTarget, useConsultantChatNavigation, } from "../../hooks/conversation/useConsultantChatNavigation";
|
|
@@ -45,6 +46,7 @@ const DChatConversationDetail = (_a) => {
|
|
|
45
46
|
const conversation = useConversationStore((state) => state.conversationData);
|
|
46
47
|
const authUserID = useAuthStore((state) => state.userID);
|
|
47
48
|
const isViewOnlyMember = useConversationStore((state) => state.isViewOnlyMember);
|
|
49
|
+
const canPinMessage = useCanPinMessage();
|
|
48
50
|
const clearSelectedConversation = useConversationStore((state) => state.clearSelectedConversation);
|
|
49
51
|
const setPendingResolveTarget = useConversationStore((state) => state.setPendingResolveTarget);
|
|
50
52
|
const { isResolving, error } = useResolveConversation({
|
|
@@ -139,6 +141,6 @@ const DChatConversationDetail = (_a) => {
|
|
|
139
141
|
!botFlow.flowIsEnd &&
|
|
140
142
|
botFlow.shouldShowButtons &&
|
|
141
143
|
botFlow.currentButtonsBlock ? (_jsx(BotButtonMessage, { buttons: botFlow.currentButtonsBlock.data.buttons, disabled: botFlow.isProcessing, onSelect: botFlow.selectButton, onRestart: botFlow.restart })) : null;
|
|
142
|
-
return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile }), _jsx(PinnedMessageBar, {}), _jsxs("div", { [COMPOSER_DISMISS_ZONE_ATTR]: "", className: "relative flex min-h-0 flex-1 flex-col", children: [children !== null && children !== void 0 ? children : (_jsx(ConversationMessageList, { bottomSlot: botButtonsSlot, onCopyMessageLink: onCopyMessageLink, onSearchMessageRevoked: onSearchMessageRevoked })), _jsx(TypingIndicatorBar, { conversationId: conversation.conversationID })] }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab }), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
|
|
144
|
+
return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile }), _jsx(PinnedMessageBar, { canUnpin: canPinMessage }), _jsxs("div", { [COMPOSER_DISMISS_ZONE_ATTR]: "", className: "relative flex min-h-0 flex-1 flex-col", children: [children !== null && children !== void 0 ? children : (_jsx(ConversationMessageList, { bottomSlot: botButtonsSlot, onCopyMessageLink: onCopyMessageLink, onSearchMessageRevoked: onSearchMessageRevoked })), _jsx(TypingIndicatorBar, { conversationId: conversation.conversationID })] }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab }), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
|
|
143
145
|
};
|
|
144
146
|
export default DChatConversationDetail;
|
|
@@ -11,8 +11,10 @@ interface DeskWorkspaceProps {
|
|
|
11
11
|
isOffline?: boolean;
|
|
12
12
|
/** Shown in the offline overlay: reconnect status, and Retry once the reconnect has failed. */
|
|
13
13
|
offlineContent?: ReactNode;
|
|
14
|
+
/** First boot: covers the desk (locked like offline) while its first screen of data loads. */
|
|
15
|
+
bootOverlay?: ReactNode;
|
|
14
16
|
}
|
|
15
17
|
/** Bright Workspace: folder, list, chat and info as separate white panels on the page canvas. */
|
|
16
|
-
declare const DeskWorkspace: ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline, offlineContent, }: DeskWorkspaceProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
declare const DeskWorkspace: ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline, offlineContent, bootOverlay, }: DeskWorkspaceProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
19
|
export default DeskWorkspace;
|
|
18
20
|
//# sourceMappingURL=DeskWorkspace.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeskWorkspace.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/DeskWorkspace.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"DeskWorkspace.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/DeskWorkspace.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAqB5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGxD,UAAU,kBAAkB;IAC1B,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACrD,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,8FAA8F;IAC9F,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAID,iGAAiG;AACjG,QAAA,MAAM,aAAa,GAAI,qJASpB,kBAAkB,4CA2LpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -10,7 +10,7 @@ import { useMediaQuery } from "../../hooks/common/useMediaQuery";
|
|
|
10
10
|
import { useSidePanel } from "../../hooks/common/useSidePanel";
|
|
11
11
|
import { useVisualViewportHeight } from "../../hooks/common/useVisualViewportHeight";
|
|
12
12
|
import { useViewportTier } from "../../hooks/common/useViewportTier";
|
|
13
|
-
import { useDeskOfflineLock } from "../../hooks/conversation/useDeskOfflineLock";
|
|
13
|
+
import { DESK_OFFLINE_OVERLAY_ATTR, useDeskOfflineLock, } from "../../hooks/conversation/useDeskOfflineLock";
|
|
14
14
|
import { useDeskThreadNavigation } from "../../hooks/conversation/useDeskThreadNavigation";
|
|
15
15
|
import useSidePanelStore from "../../store/sidePanel";
|
|
16
16
|
import useConversationStore from "../../store/conversation";
|
|
@@ -26,7 +26,7 @@ import DeskOfflineOverlay from "./DeskOfflineOverlay";
|
|
|
26
26
|
import { PeerType } from "../../types/conversation";
|
|
27
27
|
const minWidth = (px) => `(min-width: ${px}px)`;
|
|
28
28
|
/** Bright Workspace: folder, list, chat and info as separate white panels on the page canvas. */
|
|
29
|
-
const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline = false, offlineContent, }) => {
|
|
29
|
+
const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline = false, offlineContent, bootOverlay, }) => {
|
|
30
30
|
const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
|
|
31
31
|
const searchClientMsgID = useConversationStore((state) => state.searchClientMsgID);
|
|
32
32
|
const { isOpen: isInfoOpen, toggle: toggleInfo, close: closeInfo } = useSidePanel("info");
|
|
@@ -78,7 +78,7 @@ const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMes
|
|
|
78
78
|
useSidePanelStore.getState().closePanel();
|
|
79
79
|
}, [isPhone, selectedConversationId]);
|
|
80
80
|
const wasShowingThread = useRef(showThread);
|
|
81
|
-
useDeskOfflineLock(workspaceEl, isOffline, () => {
|
|
81
|
+
useDeskOfflineLock(workspaceEl, isOffline || Boolean(bootOverlay), () => {
|
|
82
82
|
var _a, _b;
|
|
83
83
|
return isPhone
|
|
84
84
|
? showThread
|
|
@@ -121,6 +121,6 @@ const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMes
|
|
|
121
121
|
? undefined
|
|
122
122
|
: { width: isFolderInline ? LAYOUT_WIDTHS.list : LAYOUT_WIDTHS.listCompact }, children: [_jsx(DeskConversationList, { onOpenAppMenu: onOpenAppMenu }), !isFolderInline && _jsx(FolderOverlay, {})] }), _jsxs("div", { ref: chatPaneRef, className: isPhone
|
|
123
123
|
? `absolute inset-0 flex flex-col overflow-hidden bg-white ${showThread ? "" : "invisible"}`
|
|
124
|
-
: "flex h-full flex-1 overflow-hidden rounded-xl bg-white shadow-sm", style: isPhone ? undefined : { minWidth: isDesktop ? LAYOUT_WIDTHS.chatMin : 0 }, children: [isPhone && !hasConversationData && (_jsx(PhoneThreadFallbackHeader, { onBack: backToList, onOpenAppMenu: onOpenAppMenu })), messageList] }), _jsx(ThreadInfo, { isOpen: isInfoOpen, onClose: closeInfo, groupConfig: groupThreadInfoConfig, supportPanelInline: isSupportPanelInline, drawerContainer: workspaceEl }), _jsx("div", { ref: setPanelSlotEl, className: "contents" }), _jsx(ShortcutHelpModal, { open: isHelpOpen, onClose: () => setIsHelpOpen(false) }), isOffline && (_jsx(DeskOfflineOverlay, { isPhone: isPhone, onOpenAppMenu: onOpenAppMenu, children: offlineContent }))] }));
|
|
124
|
+
: "flex h-full flex-1 overflow-hidden rounded-xl bg-white shadow-sm", style: isPhone ? undefined : { minWidth: isDesktop ? LAYOUT_WIDTHS.chatMin : 0 }, children: [isPhone && !hasConversationData && (_jsx(PhoneThreadFallbackHeader, { onBack: backToList, onOpenAppMenu: onOpenAppMenu })), messageList] }), _jsx(ThreadInfo, { isOpen: isInfoOpen, onClose: closeInfo, groupConfig: groupThreadInfoConfig, supportPanelInline: isSupportPanelInline, drawerContainer: workspaceEl }), _jsx("div", { ref: setPanelSlotEl, className: "contents" }), _jsx(ShortcutHelpModal, { open: isHelpOpen, onClose: () => setIsHelpOpen(false) }), bootOverlay ? (_jsx("div", { [DESK_OFFLINE_OVERLAY_ATTR]: "", "data-testid": "desk-boot-overlay", className: "absolute inset-0 z-[1000] flex", children: bootOverlay })) : (isOffline && (_jsx(DeskOfflineOverlay, { isPhone: isPhone, onOpenAppMenu: onOpenAppMenu, children: offlineContent })))] }));
|
|
125
125
|
};
|
|
126
126
|
export default DeskWorkspace;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { GroupThreadInfoConfig } from "../../components/thread/GroupThreadInfo";
|
|
2
2
|
import type { CannedCreateRequest } from "../../types/chat";
|
|
3
|
+
/** Longest the boot screen waits for the desk's first page of conversations once it can load. */
|
|
4
|
+
export declare const FIRST_SCREEN_MAX_WAIT_MS = 10000;
|
|
3
5
|
interface DChatDeskMessageProps {
|
|
4
6
|
openCreateCannedModal?: (request?: CannedCreateRequest) => void;
|
|
5
7
|
groupThreadInfoConfig?: GroupThreadInfoConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/index.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/index.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAM5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGxD,iGAAiG;AACjG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,UAAU,qBAAqB;IAC7B,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,+GAA+G;IAC/G,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CACtD;AAWD,QAAA,MAAM,gBAAgB,GAAI,OAAO,qBAAqB,4CA4DrD,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -6,10 +6,13 @@ import { useChatContext } from "../../context/ChatContext";
|
|
|
6
6
|
import { ConnectStatus } from "../../types/chat";
|
|
7
7
|
import { QUERY_KEYS } from "../../services/query";
|
|
8
8
|
import { useChatBootPhase } from "../../hooks/init/useChatBootPhase";
|
|
9
|
+
import useBootStore, { selectHasConversations } from "../../store/boot";
|
|
9
10
|
import DeskWorkspace from "./DeskWorkspace";
|
|
10
11
|
import DeskWorkspaceSkeleton from "./DeskWorkspaceSkeleton";
|
|
11
12
|
import DeskBootCard from "./DeskBootCard";
|
|
12
13
|
const DESK_SLOW_AFTER_MS = 15000;
|
|
14
|
+
/** Longest the boot screen waits for the desk's first page of conversations once it can load. */
|
|
15
|
+
export const FIRST_SCREEN_MAX_WAIT_MS = 10000;
|
|
13
16
|
// Lists behind the desk; they refetch after a reconnect because events were missed while offline.
|
|
14
17
|
const DESK_QUERY_KEYS = [
|
|
15
18
|
QUERY_KEYS.GET_TEAM_SESSIONS,
|
|
@@ -23,13 +26,26 @@ const DChatDeskMessage = (props) => {
|
|
|
23
26
|
const { connectStatus } = useChatContext();
|
|
24
27
|
const queryClient = useQueryClient();
|
|
25
28
|
const isConnected = connectStatus === ConnectStatus.Connected;
|
|
26
|
-
|
|
27
|
-
const
|
|
29
|
+
const hasConversations = useBootStore(selectHasConversations);
|
|
30
|
+
const isFirstScreenReady = useBootStore((state) => state.isFirstScreenReady);
|
|
31
|
+
// The desk mounts once the local DB holds the conversations; a fresh device's sync then
|
|
32
|
+
// finishes in the background while the first page loads on demand under the boot screen.
|
|
33
|
+
const canMountDesk = isConnected && hasConversations;
|
|
34
|
+
const [isFirstScreenTimedOut, setIsFirstScreenTimedOut] = useState(false);
|
|
35
|
+
const isBooted = canMountDesk && (isFirstScreenReady || isFirstScreenTimedOut);
|
|
36
|
+
// After the first screen has shown, the desk stays mounted, so a reconnect keeps scroll, search and panels.
|
|
37
|
+
const [hasBooted, setHasBooted] = useState(isBooted);
|
|
28
38
|
const wasDropped = useRef(false);
|
|
29
39
|
useEffect(() => {
|
|
30
|
-
if (
|
|
40
|
+
if (isBooted)
|
|
31
41
|
setHasBooted(true);
|
|
32
|
-
}, [
|
|
42
|
+
}, [isBooted]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (hasBooted || !canMountDesk || isFirstScreenReady)
|
|
45
|
+
return;
|
|
46
|
+
const timer = setTimeout(() => setIsFirstScreenTimedOut(true), FIRST_SCREEN_MAX_WAIT_MS);
|
|
47
|
+
return () => clearTimeout(timer);
|
|
48
|
+
}, [hasBooted, canMountDesk, isFirstScreenReady]);
|
|
33
49
|
useEffect(() => {
|
|
34
50
|
if (!hasBooted)
|
|
35
51
|
return;
|
|
@@ -47,9 +63,11 @@ const DChatDeskMessage = (props) => {
|
|
|
47
63
|
},
|
|
48
64
|
});
|
|
49
65
|
}, [hasBooted, isConnected, queryClient]);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
66
|
+
const showDesk = hasBooted || isBooted;
|
|
67
|
+
// An incremental sync can end before the first page is in; keep the sync step on screen till then.
|
|
68
|
+
const bootScreen = (_jsx(DeskWorkspaceSkeleton, { children: _jsx(DeskBootCard, { boot: boot.phase === "ready" ? Object.assign(Object.assign({}, boot), { phase: "sync" }) : boot }) }));
|
|
69
|
+
if (!showDesk && !canMountDesk)
|
|
70
|
+
return bootScreen;
|
|
71
|
+
return (_jsx(DeskWorkspace, Object.assign({}, props, { isOffline: !isConnected, offlineContent: isConnected ? undefined : _jsx(DeskBootCard, { boot: boot }), bootOverlay: showDesk ? undefined : bootScreen })));
|
|
54
72
|
};
|
|
55
73
|
export default DChatDeskMessage;
|
package/dist/store/boot.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/** Boot steps that can fail before the first sync. */
|
|
2
2
|
export type ChatBootFailedStep = "engine" | "connect";
|
|
3
|
+
/** A fresh device pulls everything ("reinstall"); a kept local copy only catches up ("incremental"). */
|
|
4
|
+
export type ChatBootSyncKind = "reinstall" | "incremental";
|
|
5
|
+
/** Progress a fresh-device sync reports once groups and conversations are stored, before any message. */
|
|
6
|
+
export declare const CONVERSATIONS_SYNCED_PROGRESS = 10;
|
|
3
7
|
interface BootStore {
|
|
4
8
|
/** User the current boot belongs to; logging in as someone else starts over. */
|
|
5
9
|
userID: string | null;
|
|
@@ -13,16 +17,27 @@ interface BootStore {
|
|
|
13
17
|
failedStep: ChatBootFailedStep | null;
|
|
14
18
|
/** 0-100 from OnSyncServerProgress (only sent while a fresh device syncs). */
|
|
15
19
|
syncProgress: number;
|
|
20
|
+
/** Kind of the first sync after login while it runs; cleared once it finishes or fails. */
|
|
21
|
+
firstSyncKind: ChatBootSyncKind | null;
|
|
22
|
+
/** The desk's first page of conversations has its rows and previews. */
|
|
23
|
+
isFirstScreenReady: boolean;
|
|
16
24
|
startBoot: (userID: string) => void;
|
|
17
25
|
reset: () => void;
|
|
18
26
|
markConnecting: () => void;
|
|
19
27
|
markConnected: () => void;
|
|
20
28
|
markFailed: (step: ChatBootFailedStep) => void;
|
|
29
|
+
markSyncStarted: (isReinstall: boolean) => void;
|
|
21
30
|
setSyncProgress: (progress: number) => void;
|
|
22
31
|
markFirstSyncDone: () => void;
|
|
23
32
|
markSyncStalled: () => void;
|
|
24
33
|
markListSettled: () => void;
|
|
34
|
+
markFirstScreenReady: () => void;
|
|
25
35
|
}
|
|
36
|
+
type BootSyncState = Pick<BootStore, "isFirstSyncDone" | "firstSyncKind" | "syncProgress">;
|
|
37
|
+
/** A fresh device's first sync has stored the conversations and is still pulling each one's latest message. */
|
|
38
|
+
export declare const selectIsSyncingInBackground: (state: BootSyncState) => boolean;
|
|
39
|
+
/** The local DB holds the conversations: the first sync ended, only catches up, or runs in the background. */
|
|
40
|
+
export declare const selectHasConversations: (state: BootSyncState) => boolean;
|
|
26
41
|
/** Progress of the chat engine boot (WASM, login, connect, first sync) shared by the loading screens. */
|
|
27
42
|
declare const useBootStore: import("zustand").UseBoundStore<import("zustand").StoreApi<BootStore>>;
|
|
28
43
|
export default useBootStore;
|
package/dist/store/boot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../src/store/boot.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtD,UAAU,SAAS;IACjB,gFAAgF;IAChF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,yGAAyG;IACzG,eAAe,EAAE,OAAO,CAAC;IACzB,6FAA6F;IAC7F,aAAa,EAAE,OAAO,CAAC;IACvB,gGAAgG;IAChG,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACtC,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../src/store/boot.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtD,wGAAwG;AACxG,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,aAAa,CAAC;AAE3D,yGAAyG;AACzG,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,UAAU,SAAS;IACjB,gFAAgF;IAChF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,yGAAyG;IACzG,eAAe,EAAE,OAAO,CAAC;IACzB,6FAA6F;IAC7F,aAAa,EAAE,OAAO,CAAC;IACvB,gGAAgG;IAChG,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACtC,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IACrB,2FAA2F;IAC3F,aAAa,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACvC,wEAAwE;IACxE,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,eAAe,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,oBAAoB,EAAE,MAAM,IAAI,CAAC;CAClC;AAcD,KAAK,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,iBAAiB,GAAG,eAAe,GAAG,cAAc,CAAC,CAAC;AAE3F,+GAA+G;AAC/G,eAAO,MAAM,2BAA2B,GAAI,OAAO,aAAa,YAC4B,CAAC;AAE7F,8GAA8G;AAC9G,eAAO,MAAM,sBAAsB,GAAI,OAAO,aAAa,YAGvB,CAAC;AAErC,yGAAyG;AACzG,QAAA,MAAM,YAAY,wEA2Bf,CAAC;AAEJ,eAAe,YAAY,CAAC"}
|
package/dist/store/boot.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
+
/** Progress a fresh-device sync reports once groups and conversations are stored, before any message. */
|
|
3
|
+
export const CONVERSATIONS_SYNCED_PROGRESS = 10;
|
|
2
4
|
const initialState = {
|
|
3
5
|
userID: null,
|
|
4
6
|
hasConnected: false,
|
|
@@ -7,15 +9,31 @@ const initialState = {
|
|
|
7
9
|
isListSettled: false,
|
|
8
10
|
failedStep: null,
|
|
9
11
|
syncProgress: 0,
|
|
12
|
+
firstSyncKind: null,
|
|
13
|
+
isFirstScreenReady: false,
|
|
10
14
|
};
|
|
15
|
+
/** A fresh device's first sync has stored the conversations and is still pulling each one's latest message. */
|
|
16
|
+
export const selectIsSyncingInBackground = (state) => state.firstSyncKind === "reinstall" && state.syncProgress >= CONVERSATIONS_SYNCED_PROGRESS;
|
|
17
|
+
/** The local DB holds the conversations: the first sync ended, only catches up, or runs in the background. */
|
|
18
|
+
export const selectHasConversations = (state) => state.isFirstSyncDone ||
|
|
19
|
+
state.firstSyncKind === "incremental" ||
|
|
20
|
+
selectIsSyncingInBackground(state);
|
|
11
21
|
/** Progress of the chat engine boot (WASM, login, connect, first sync) shared by the loading screens. */
|
|
12
22
|
const useBootStore = create((set, get) => (Object.assign(Object.assign({}, initialState), { startBoot: (userID) => {
|
|
13
23
|
if (get().userID === userID)
|
|
14
24
|
return;
|
|
15
25
|
set(Object.assign(Object.assign({}, initialState), { userID }));
|
|
16
|
-
}, reset: () => set(initialState), markConnecting: () => set({ failedStep: null }), markConnected: () => set({ hasConnected: true, failedStep: null }), markFailed: (step) => set({ failedStep: step }),
|
|
26
|
+
}, reset: () => set(initialState), markConnecting: () => set({ failedStep: null }), markConnected: () => set({ hasConnected: true, failedStep: null }), markFailed: (step) => set({ failedStep: step }), markSyncStarted: (isReinstall) => {
|
|
27
|
+
// Syncs after a reconnect don't change how the first one runs.
|
|
28
|
+
if (get().isFirstSyncDone || get().firstSyncKind)
|
|
29
|
+
return;
|
|
30
|
+
set({ firstSyncKind: isReinstall ? "reinstall" : "incremental" });
|
|
31
|
+
}, setSyncProgress: (progress) => set({ syncProgress: Math.max(0, Math.min(100, Math.round(progress))) }), markFirstSyncDone: () => set({ isFirstSyncDone: true, isSyncStalled: false, firstSyncKind: null }), markSyncStalled: () => {
|
|
17
32
|
if (get().isFirstSyncDone)
|
|
18
33
|
return;
|
|
19
34
|
set({ isFirstSyncDone: true, isSyncStalled: true });
|
|
20
|
-
}, markListSettled: () => set({ isListSettled: true })
|
|
35
|
+
}, markListSettled: () => set({ isListSettled: true }), markFirstScreenReady: () => {
|
|
36
|
+
if (!get().isFirstScreenReady)
|
|
37
|
+
set({ isFirstScreenReady: true });
|
|
38
|
+
} })));
|
|
21
39
|
export default useBootStore;
|