diff --git a/go/libkb/version.go b/go/libkb/version.go index d8b23ce1ad0e..e9c38269738b 100644 --- a/go/libkb/version.go +++ b/go/libkb/version.go @@ -4,4 +4,4 @@ package libkb // Version is the current version (should be MAJOR.MINOR.PATCH) -const Version = "6.6.0" +const Version = "6.7.0" diff --git a/shared/android/app/build.gradle b/shared/android/app/build.gradle index 43cfc1c7c8ad..29e9bfaf1434 100644 --- a/shared/android/app/build.gradle +++ b/shared/android/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: "com.facebook.react" apply plugin: 'com.github.triplet.play' // KB: app version -def VERSION_NAME = "6.6.0" +def VERSION_NAME = "6.7.0" // KB: Number of commits, like ios Integer getVersionCode() { diff --git a/shared/app/global-errors/hook.tsx b/shared/app/global-errors/hook.tsx index 646d872c8f90..df6a580e4787 100644 --- a/shared/app/global-errors/hook.tsx +++ b/shared/app/global-errors/hook.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' import * as React from 'react' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import type {RPCError} from '@/util/errors' -import {settingsFeedbackTab} from '@/constants/settings/util' -import {useDaemonState} from '@/constants/daemon' +import {settingsFeedbackTab} from '@/constants/settings' +import {useDaemonState} from '@/stores/daemon' export type Size = 'Closed' | 'Small' | 'Big' @@ -26,7 +26,7 @@ const useData = () => { navigateAppend('feedback') } }, [navigateAppend, clearModals, loggedIn, setGlobalError]) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onDismiss = React.useCallback(() => { setGlobalError() }, [setGlobalError]) diff --git a/shared/app/index.native.tsx b/shared/app/index.native.tsx index 1152498d3a5f..d9f009250dad 100644 --- a/shared/app/index.native.tsx +++ b/shared/app/index.native.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import * as React from 'react' -import {useDeepLinksState} from '@/constants/deeplinks' +import {handleAppLink} from '@/constants/deeplinks' import Main from './main.native' import {KeyboardProvider} from 'react-native-keyboard-controller' import Animated, {ReducedMotionConfig, ReduceMotion} from 'react-native-reanimated' @@ -18,9 +18,8 @@ import ServiceDecoration from '@/common-adapters/markdown/service-decoration' import {useUnmountAll} from '@/util/debug-react' import {darkModeSupported, guiConfig} from 'react-native-kb' import {install} from 'react-native-kb' -import {useEngineState} from '@/constants/engine' -import * as DarkMode from '@/constants/darkmode' -import {initPlatformListener} from '@/constants/platform-specific' +import * as DarkMode from '@/stores/darkmode' +import {initPlatformListener, onEngineConnected, onEngineDisconnected, onEngineIncoming} from '@/constants/init/index.native' import logger from '@/logger' logger.info('INIT App index module load') @@ -110,7 +109,6 @@ const StoreHelper = (p: {children: React.ReactNode}): React.ReactNode => { const {children} = p useDarkHookup() useKeyboardHookup() - const handleAppLink = useDeepLinksState(s => s.dispatch.handleAppLink) React.useEffect(() => { const linkingSub = Linking.addEventListener('url', ({url}: {url: string}) => { @@ -119,7 +117,7 @@ const StoreHelper = (p: {children: React.ReactNode}): React.ReactNode => { return () => { linkingSub.remove() } - }, [handleAppLink]) + }, []) return children } @@ -139,11 +137,11 @@ const useInit = () => { const {batch} = C.useWaitingState.getState().dispatch const eng = makeEngine(batch, c => { if (c) { - useEngineState.getState().dispatch.onEngineConnected() + onEngineConnected() } else { - useEngineState.getState().dispatch.onEngineDisconnected() + onEngineDisconnected() } - }) + }, onEngineIncoming) initPlatformListener() eng.listenersAreReady() diff --git a/shared/app/main.desktop.tsx b/shared/app/main.desktop.tsx index 16ef0670516d..12bd8e36da36 100644 --- a/shared/app/main.desktop.tsx +++ b/shared/app/main.desktop.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import Router from '@/router-v2/router' -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import ResetModal from '../login/reset/modal' import GlobalError from './global-errors' import OutOfDate from './out-of-date' diff --git a/shared/app/out-of-date.tsx b/shared/app/out-of-date.tsx index 3287a86e87d1..70b33bb01693 100644 --- a/shared/app/out-of-date.tsx +++ b/shared/app/out-of-date.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import logger from '@/logger' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const styles = Kb.Styles.styleSheetCreate(() => ({ container: { @@ -62,7 +62,7 @@ const OutOfDate = () => { C.ignorePromise(f()) }) - const onOpenAppStore = useConfigState(s => s.dispatch.dynamic.openAppStore) + const onOpenAppStore = useConfigState(s => s.dispatch.defer.openAppStore) return status !== 'critical' ? null : ( diff --git a/shared/app/runtime-stats.tsx b/shared/app/runtime-stats.tsx index 55e94eee4a96..2ea975b665cc 100644 --- a/shared/app/runtime-stats.tsx +++ b/shared/app/runtime-stats.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const isIPhoneX = false as boolean // import lagRadar from 'lag-radar' diff --git a/shared/chat/audio/audio-recorder.native.tsx b/shared/chat/audio/audio-recorder.native.tsx index b68e7f68ade6..8c3a2d197c98 100644 --- a/shared/chat/audio/audio-recorder.native.tsx +++ b/shared/chat/audio/audio-recorder.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {Portal} from '@/common-adapters/portal.native' diff --git a/shared/chat/blocking/block-modal.tsx b/shared/chat/blocking/block-modal.tsx index e0c695937385..0a9dfdd64d03 100644 --- a/shared/chat/blocking/block-modal.tsx +++ b/shared/chat/blocking/block-modal.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' -import {useUsersState} from '@/constants/users' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' +import {useUsersState} from '@/stores/users' // Type for extra RouteProp passed to block modal sometimes when launching the // modal from specific places from the app. diff --git a/shared/chat/blocking/invitation-to-block.tsx b/shared/chat/blocking/invitation-to-block.tsx index e7ba0b2f0950..9e124e37d688 100644 --- a/shared/chat/blocking/invitation-to-block.tsx +++ b/shared/chat/blocking/invitation-to-block.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const BlockButtons = () => { const nav = useSafeNavigation() diff --git a/shared/chat/chat-button.tsx b/shared/chat/chat-button.tsx index 1524bf4e37a2..d565cffd15b4 100644 --- a/shared/chat/chat-button.tsx +++ b/shared/chat/chat-button.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Styles from '@/styles' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import WaitingButton from '@/common-adapters/waiting-button' import Icon from '@/common-adapters/icon' diff --git a/shared/chat/conversation/attachment-fullscreen/hooks.tsx b/shared/chat/conversation/attachment-fullscreen/hooks.tsx index 584d3cdeacc9..3c6c21497450 100644 --- a/shared/chat/conversation/attachment-fullscreen/hooks.tsx +++ b/shared/chat/conversation/attachment-fullscreen/hooks.tsx @@ -1,9 +1,9 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import {maxWidth, maxHeight} from '../messages/attachment/shared' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const blankMessage = Chat.makeMessageAttachment({}) export const useData = (initialOrdinal: T.Chat.Ordinal) => { @@ -37,7 +37,7 @@ export const useData = (initialOrdinal: T.Chat.Ordinal) => { }, [onSwitchAttachment]) const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const showInfoPanel = Chat.useChatContext(s => s.dispatch.showInfoPanel) diff --git a/shared/chat/conversation/attachment-get-titles.tsx b/shared/chat/conversation/attachment-get-titles.tsx index f5658e3078a8..609cbee8610b 100644 --- a/shared/chat/conversation/attachment-get-titles.tsx +++ b/shared/chat/conversation/attachment-get-titles.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index 4f0c8230ebbc..6d7c001fb176 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import {useBotConversationIDKey} from './install' diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index c1e24c343beb..88c1f3a90369 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import ChannelPicker from './channel-picker' import openURL from '@/util/open-url' import * as T from '@/constants/types' -import {useBotsState} from '@/constants/bots' +import {useBotsState} from '@/stores/bots' import {useAllChannelMetas} from '@/teams/common/channel-hooks' const RestrictedItem = '---RESTRICTED---' diff --git a/shared/chat/conversation/bot/search.tsx b/shared/chat/conversation/bot/search.tsx index 3a3dd2ab36f3..ec1309ad25f6 100644 --- a/shared/chat/conversation/bot/search.tsx +++ b/shared/chat/conversation/bot/search.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import debounce from 'lodash/debounce' import type * as T from '@/constants/types' import {Bot} from '../info-panel/bot' -import {getFeaturedSorted, useBotsState} from '@/constants/bots' +import {getFeaturedSorted, useBotsState} from '@/stores/bots' type Props = {teamID?: T.Teams.TeamID} diff --git a/shared/chat/conversation/bot/team-picker.tsx b/shared/chat/conversation/bot/team-picker.tsx index 23c869eeff54..b62b38be91c9 100644 --- a/shared/chat/conversation/bot/team-picker.tsx +++ b/shared/chat/conversation/bot/team-picker.tsx @@ -5,7 +5,7 @@ import * as T from '@/constants/types' import {Avatars, TeamAvatar} from '@/chat/avatars' import debounce from 'lodash/debounce' import logger from '@/logger' -import {useBotsState} from '@/constants/bots' +import {useBotsState} from '@/stores/bots' type Props = {botUsername: string} diff --git a/shared/chat/conversation/bottom-banner.tsx b/shared/chat/conversation/bottom-banner.tsx index 3b845cd09e92..878d44341538 100644 --- a/shared/chat/conversation/bottom-banner.tsx +++ b/shared/chat/conversation/bottom-banner.tsx @@ -1,12 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import _openSMS from '@/util/sms' import {assertionToDisplay} from '@/common-adapters/usernames' import type {Props as TextProps} from '@/common-adapters/text' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {showShareActionSheet} from '@/util/platform-specific' const installMessage = `I sent you encrypted messages on Keybase. You can install it here: https://keybase.io/phone-app` @@ -16,7 +17,7 @@ const Invite = () => { const users = participantInfoAll.filter(p => p.includes('@')) const openShareSheet = () => { - C.PlatformSpecific.showShareActionSheet({ + showShareActionSheet({ message: installMessage, mimeType: 'text/plain', }) diff --git a/shared/chat/conversation/command-markdown.tsx b/shared/chat/conversation/command-markdown.tsx index 1f434bc4d280..10c20f1c5509 100644 --- a/shared/chat/conversation/command-markdown.tsx +++ b/shared/chat/conversation/command-markdown.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' const CommandMarkdown = () => { diff --git a/shared/chat/conversation/command-status.tsx b/shared/chat/conversation/command-status.tsx index 9a005c52a13c..dc67888d461c 100644 --- a/shared/chat/conversation/command-status.tsx +++ b/shared/chat/conversation/command-status.tsx @@ -1,7 +1,7 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const empty = { actions: [], @@ -13,7 +13,7 @@ const Container = () => { const info = Chat.useChatContext(s => s.commandStatus) const _info = info || empty - const onOpenAppSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenAppSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const setCommandStatusInfo = Chat.useChatContext(s => s.dispatch.setCommandStatusInfo) const onCancel = () => { setCommandStatusInfo() diff --git a/shared/chat/conversation/container.tsx b/shared/chat/conversation/container.tsx index 0cb2f2d21908..d892d3f39949 100644 --- a/shared/chat/conversation/container.tsx +++ b/shared/chat/conversation/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import Normal from './normal/container' import NoConversation from './no-conversation' import Error from './error' diff --git a/shared/chat/conversation/error.tsx b/shared/chat/conversation/error.tsx index c4762dc4f87a..0c9266bb235c 100644 --- a/shared/chat/conversation/error.tsx +++ b/shared/chat/conversation/error.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' const ConversationError = () => { diff --git a/shared/chat/conversation/fwd-msg.tsx b/shared/chat/conversation/fwd-msg.tsx index 53ec16deb001..265d9e69c756 100644 --- a/shared/chat/conversation/fwd-msg.tsx +++ b/shared/chat/conversation/fwd-msg.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/giphy/hooks.tsx b/shared/chat/conversation/giphy/hooks.tsx index bad968dd56aa..b2aea6c79b09 100644 --- a/shared/chat/conversation/giphy/hooks.tsx +++ b/shared/chat/conversation/giphy/hooks.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' export const useHooks = () => { const giphy = Chat.useChatContext(s => s.giphyResult) diff --git a/shared/chat/conversation/header-area/index.native.tsx b/shared/chat/conversation/header-area/index.native.tsx index c2b82732a4aa..ceaaff316fdd 100644 --- a/shared/chat/conversation/header-area/index.native.tsx +++ b/shared/chat/conversation/header-area/index.native.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import * as React from 'react' import type {HeaderBackButtonProps} from '@react-navigation/elements' @@ -9,8 +9,8 @@ import {Keyboard} from 'react-native' // import {DebugChatDumpContext} from '@/constants/chat2/debug' import {assertionToDisplay} from '@/common-adapters/usernames' import {useSafeAreaFrame} from 'react-native-safe-area-context' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' export const HeaderAreaRight = () => { const conversationIDKey = Chat.useChatContext(s => s.id) diff --git a/shared/chat/conversation/info-panel/add-people.tsx b/shared/chat/conversation/info-panel/add-people.tsx index 9e7492a91f92..724cd29ec1c1 100644 --- a/shared/chat/conversation/info-panel/add-people.tsx +++ b/shared/chat/conversation/info-panel/add-people.tsx @@ -1,5 +1,5 @@ -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/info-panel/add-to-channel.tsx b/shared/chat/conversation/info-panel/add-to-channel.tsx index 59bfd1d8e9ca..2109aaae2717 100644 --- a/shared/chat/conversation/info-panel/add-to-channel.tsx +++ b/shared/chat/conversation/info-panel/add-to-channel.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/info-panel/attachments.tsx b/shared/chat/conversation/info-panel/attachments.tsx index 451fa0a9bf98..61b7fb7608a9 100644 --- a/shared/chat/conversation/info-panel/attachments.tsx +++ b/shared/chat/conversation/info-panel/attachments.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type {StylesTextCrossPlatform} from '@/common-adapters/text' import * as T from '@/constants/types' @@ -8,7 +8,7 @@ import chunk from 'lodash/chunk' import {formatAudioRecordDuration, formatTimeForMessages} from '@/util/timestamp' import {infoPanelWidth} from './common' import {useMessagePopup} from '../messages/message-popup' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = { commonSections: ReadonlyArray
@@ -499,7 +499,7 @@ export const useAttachmentSections = ( } const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = (message: T.Chat.MessageAttachment) => message.downloadPath && openLocalPathInSystemFileManagerDesktop?.(message.downloadPath) diff --git a/shared/chat/conversation/info-panel/bot.tsx b/shared/chat/conversation/info-panel/bot.tsx index 9acfef03a51a..1152170c6d5d 100644 --- a/shared/chat/conversation/info-panel/bot.tsx +++ b/shared/chat/conversation/info-panel/bot.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' -import {getFeaturedSorted, useBotsState} from '@/constants/bots' -import {useUsersState} from '@/constants/users' +import {getFeaturedSorted, useBotsState} from '@/stores/bots' +import {useUsersState} from '@/stores/users' type AddToChannelProps = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/chat/conversation/info-panel/common.tsx b/shared/chat/conversation/info-panel/common.tsx index 1de1bb893716..d01c196b8579 100644 --- a/shared/chat/conversation/info-panel/common.tsx +++ b/shared/chat/conversation/info-panel/common.tsx @@ -1,5 +1,5 @@ -import type * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import type * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import * as Styles from '@/styles' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/info-panel/header.tsx b/shared/chat/conversation/info-panel/header.tsx index 121f86d02404..6d45cc0ca533 100644 --- a/shared/chat/conversation/info-panel/header.tsx +++ b/shared/chat/conversation/info-panel/header.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import InfoPanelMenu from './menu' import * as InfoPanelCommon from './common' diff --git a/shared/chat/conversation/info-panel/index.tsx b/shared/chat/conversation/info-panel/index.tsx index f2bcd79a3346..0bf1adf3fad2 100644 --- a/shared/chat/conversation/info-panel/index.tsx +++ b/shared/chat/conversation/info-panel/index.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import {AdhocHeader, TeamHeader} from './header' import SettingsList from './settings' diff --git a/shared/chat/conversation/info-panel/members.tsx b/shared/chat/conversation/info-panel/members.tsx index 5c1242ee02d7..72b3a4405985 100644 --- a/shared/chat/conversation/info-panel/members.tsx +++ b/shared/chat/conversation/info-panel/members.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import Participant from './participant' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' type Props = { commonSections: ReadonlyArray
diff --git a/shared/chat/conversation/info-panel/menu.tsx b/shared/chat/conversation/info-panel/menu.tsx index eac5285c62f0..0d1bd8e20c50 100644 --- a/shared/chat/conversation/info-panel/menu.tsx +++ b/shared/chat/conversation/info-panel/menu.tsx @@ -1,14 +1,14 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as T from '@/constants/types' import * as InfoPanelCommon from './common' import {Avatars, TeamAvatar} from '@/chat/avatars' import {TeamsSubscriberMountOnly} from '@/teams/subscriber' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { attachTo?: React.RefObject diff --git a/shared/chat/conversation/info-panel/settings/index.tsx b/shared/chat/conversation/info-panel/settings/index.tsx index 6535768ef672..7656d2a941d9 100644 --- a/shared/chat/conversation/info-panel/settings/index.tsx +++ b/shared/chat/conversation/info-panel/settings/index.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import * as React from 'react' import MinWriterRole from './min-writer-role' import Notifications from './notifications' import RetentionPicker from '@/teams/team/settings-tab/retention' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type EntityType = 'adhoc' | 'small team' | 'channel' type SettingsPanelProps = {isPreview: boolean} diff --git a/shared/chat/conversation/info-panel/settings/min-writer-role.tsx b/shared/chat/conversation/info-panel/settings/min-writer-role.tsx index a25934607682..56c8a09e4566 100644 --- a/shared/chat/conversation/info-panel/settings/min-writer-role.tsx +++ b/shared/chat/conversation/info-panel/settings/min-writer-role.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Style from '@/styles' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/info-panel/settings/notifications.tsx b/shared/chat/conversation/info-panel/settings/notifications.tsx index 53bbf518206f..fdfcb3a0bc94 100644 --- a/shared/chat/conversation/info-panel/settings/notifications.tsx +++ b/shared/chat/conversation/info-panel/settings/notifications.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/input-area/container.tsx b/shared/chat/conversation/input-area/container.tsx index 7ce09e75c96b..e318566ef103 100644 --- a/shared/chat/conversation/input-area/container.tsx +++ b/shared/chat/conversation/input-area/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import Normal from './normal2' import Preview from './preview' import ThreadSearch from '../search' diff --git a/shared/chat/conversation/input-area/location-popup.d.ts b/shared/chat/conversation/input-area/location-popup.d.ts new file mode 100644 index 000000000000..251868e41272 --- /dev/null +++ b/shared/chat/conversation/input-area/location-popup.d.ts @@ -0,0 +1,3 @@ +import type * as React from 'react' +declare const LocationPopup: () => React.ReactNode +export default LocationPopup diff --git a/shared/chat/conversation/input-area/location-popup.desktop.tsx b/shared/chat/conversation/input-area/location-popup.desktop.tsx new file mode 100644 index 000000000000..c07558b9f77f --- /dev/null +++ b/shared/chat/conversation/input-area/location-popup.desktop.tsx @@ -0,0 +1,2 @@ +const LocationPopup = () => null +export default LocationPopup diff --git a/shared/chat/conversation/input-area/location-popup.tsx b/shared/chat/conversation/input-area/location-popup.native.tsx similarity index 70% rename from shared/chat/conversation/input-area/location-popup.tsx rename to shared/chat/conversation/input-area/location-popup.native.tsx index fff570f758c7..ec5fef04455f 100644 --- a/shared/chat/conversation/input-area/location-popup.tsx +++ b/shared/chat/conversation/input-area/location-popup.native.tsx @@ -1,11 +1,53 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' +import logger from '@/logger' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import LocationMap from '@/chat/location-map' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' +import {requestLocationPermission} from '@/util/platform-specific' +import * as ExpoLocation from 'expo-location' + +const useWatchPosition = (conversationIDKey: T.Chat.ConversationIDKey) => { + const updateLastCoord = Chat.useChatState(s => s.dispatch.updateLastCoord) + const setCommandStatusInfo = Chat.useChatContext(s => s.dispatch.setCommandStatusInfo) + React.useEffect(() => { + let unsub = () => {} + logger.info('[location] perms check due to map') + const f = async () => { + try { + await requestLocationPermission(T.RPCChat.UIWatchPositionPerm.base) + const sub = await ExpoLocation.watchPositionAsync( + {accuracy: ExpoLocation.LocationAccuracy.Highest}, + (location: ExpoLocation.LocationObject) => { + const coord = { + accuracy: Math.floor(location.coords.accuracy ?? 0), + lat: location.coords.latitude, + lon: location.coords.longitude, + } + updateLastCoord(coord) + } + ) + unsub = () => sub.remove() + } catch (_error) { + const error = _error as {message?: string} + logger.info('failed to get location: ' + error.message) + setCommandStatusInfo({ + actions: [T.RPCChat.UICommandStatusActionTyp.appsettings], + displayText: `Failed to access location. ${error.message}`, + displayType: T.RPCChat.UICommandStatusDisplayTyp.error, + }) + } + } + + C.ignorePromise(f()) + return () => { + unsub() + } + }, [conversationIDKey, updateLastCoord, setCommandStatusInfo]) +} const LocationPopup = () => { const conversationIDKey = Chat.useChatContext(s => s.id) @@ -20,24 +62,14 @@ const LocationPopup = () => { const onClose = () => { clearModals() } - const onSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const sendMessage = Chat.useChatContext(s => s.dispatch.sendMessage) const onLocationShare = (duration: string) => { onClose() sendMessage(duration ? `/location live ${duration}` : '/location') } - React.useEffect(() => { - let unwatch: undefined | (() => void) - C.PlatformSpecific.watchPositionForMap(conversationIDKey) - .then(unsub => { - unwatch = unsub - }) - .catch(() => {}) - return () => { - unwatch?.() - } - }, [conversationIDKey]) + useWatchPosition(conversationIDKey) const width = Math.ceil(Kb.Styles.dimensionWidth) const height = Math.ceil(Kb.Styles.dimensionHeight - 320) diff --git a/shared/chat/conversation/input-area/normal2/index.tsx b/shared/chat/conversation/input-area/normal2/index.tsx index 8ceea809bb21..248c3e9db0bd 100644 --- a/shared/chat/conversation/input-area/normal2/index.tsx +++ b/shared/chat/conversation/input-area/normal2/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import CommandMarkdown from '../../command-markdown' @@ -13,7 +13,7 @@ import {infoPanelWidthTablet} from '../../info-panel/common' import {assertionToDisplay} from '@/common-adapters/usernames' import {FocusContext, ScrollContext} from '@/chat/conversation/normal/context' import type {RefType as Input2Ref} from '@/common-adapters/input2' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const useHintText = (p: { isExploding: boolean diff --git a/shared/chat/conversation/input-area/normal2/moremenu-popup.tsx b/shared/chat/conversation/input-area/normal2/moremenu-popup.native.tsx similarity index 97% rename from shared/chat/conversation/input-area/normal2/moremenu-popup.tsx rename to shared/chat/conversation/input-area/normal2/moremenu-popup.native.tsx index fc790cc73f5a..e75a2301b57f 100644 --- a/shared/chat/conversation/input-area/normal2/moremenu-popup.tsx +++ b/shared/chat/conversation/input-area/normal2/moremenu-popup.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' type Props = { diff --git a/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx b/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx index d56a9ef8bba8..5533d8515bec 100644 --- a/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx +++ b/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/input-area/normal2/platform-input.native.tsx b/shared/chat/conversation/input-area/normal2/platform-input.native.tsx index 4194566d7d99..496f7ebb8c5a 100644 --- a/shared/chat/conversation/input-area/normal2/platform-input.native.tsx +++ b/shared/chat/conversation/input-area/normal2/platform-input.native.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import AudioRecorder from '@/chat/audio/audio-recorder.native' import FilePickerPopup from '../filepicker-popup' import {onHWKeyPressed, removeOnHWKeyPressed} from 'react-native-kb' -import MoreMenuPopup from './moremenu-popup' +import MoreMenuPopup from './moremenu-popup.native' import SetExplodingMessagePicker from './set-explode-popup' import Typing from './typing' import type * as ImagePicker from 'expo-image-picker' @@ -28,7 +28,7 @@ import logger from '@/logger' import {AudioSendWrapper} from '@/chat/audio/audio-send.native' import {usePickerState} from '@/chat/emoji-picker/use-picker' import type {RefType as Input2Ref, Props as Input2Props} from '@/common-adapters/input2' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const singleLineHeight = 36 const threeLineHeight = 78 diff --git a/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx b/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx index 1552f0f23d43..781e2a289d60 100644 --- a/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx +++ b/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx @@ -1,9 +1,14 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import type {Props} from '.' -const messageExplodeDescriptions: T.Chat.MessageExplodeDescription[] = [ +export type MessageExplodeDescription = { + text: string + seconds: number +} + +const messageExplodeDescriptions: MessageExplodeDescription[] = [ {seconds: 30, text: '30 seconds'}, {seconds: 300, text: '5 minutes'}, {seconds: 3600, text: '60 minutes'}, diff --git a/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx b/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx index ed46e8c6e1f2..255ad7b0c55e 100644 --- a/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx +++ b/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx @@ -1,7 +1,7 @@ -import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {Props} from '.' import useHooks from './hooks' +import type {MessageExplodeDescription} from './hooks' const quantityTextStyle = Kb.Styles.platformStyles({ common: { @@ -13,7 +13,7 @@ const quantityTextStyle = Kb.Styles.platformStyles({ }) type ItemProps = { - desc: T.Chat.MessageExplodeDescription + desc: MessageExplodeDescription selected: boolean } diff --git a/shared/chat/conversation/input-area/normal2/typing.tsx b/shared/chat/conversation/input-area/normal2/typing.tsx index b9a42ecff2ee..c274f4396aae 100644 --- a/shared/chat/conversation/input-area/normal2/typing.tsx +++ b/shared/chat/conversation/input-area/normal2/typing.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/preview.tsx b/shared/chat/conversation/input-area/preview.tsx index 3f118f4469ac..64e25419c5f8 100644 --- a/shared/chat/conversation/input-area/preview.tsx +++ b/shared/chat/conversation/input-area/preview.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/channels.tsx b/shared/chat/conversation/input-area/suggestors/channels.tsx index 0fcf0b2bb3ba..4e0bc22ec650 100644 --- a/shared/chat/conversation/input-area/suggestors/channels.tsx +++ b/shared/chat/conversation/input-area/suggestors/channels.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Common from './common' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/commands.tsx b/shared/chat/conversation/input-area/suggestors/commands.tsx index 80f9f7753c81..4cfa2041c692 100644 --- a/shared/chat/conversation/input-area/suggestors/commands.tsx +++ b/shared/chat/conversation/input-area/suggestors/commands.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Common from './common' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/emoji.tsx b/shared/chat/conversation/input-area/suggestors/emoji.tsx index 2af8b5366e0e..df285c3eb4d2 100644 --- a/shared/chat/conversation/input-area/suggestors/emoji.tsx +++ b/shared/chat/conversation/input-area/suggestors/emoji.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Common from './common' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/input-area/suggestors/index.tsx b/shared/chat/conversation/input-area/suggestors/index.tsx index d82cd92e2019..a075d4262c9c 100644 --- a/shared/chat/conversation/input-area/suggestors/index.tsx +++ b/shared/chat/conversation/input-area/suggestors/index.tsx @@ -1,5 +1,5 @@ import * as Channels from './channels' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Commands from './commands' import * as Emoji from './emoji' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/users.tsx b/shared/chat/conversation/input-area/suggestors/users.tsx index 841ea76922fd..f951b0e3536a 100644 --- a/shared/chat/conversation/input-area/suggestors/users.tsx +++ b/shared/chat/conversation/input-area/suggestors/users.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as T from '@/constants/types' import * as Common from './common' import * as Kb from '@/common-adapters' import * as React from 'react' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' export const transformer = ( input: { diff --git a/shared/chat/conversation/list-area/hooks.tsx b/shared/chat/conversation/list-area/hooks.tsx index 6208ee8417e4..0570ca7ed8fd 100644 --- a/shared/chat/conversation/list-area/hooks.tsx +++ b/shared/chat/conversation/list-area/hooks.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import JumpToRecent from './jump-to-recent' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/list-area/index.desktop.tsx b/shared/chat/conversation/list-area/index.desktop.tsx index 10742edc1a83..a7558654025f 100644 --- a/shared/chat/conversation/list-area/index.desktop.tsx +++ b/shared/chat/conversation/list-area/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as Hooks from './hooks' import * as React from 'react' @@ -17,7 +17,7 @@ import logger from '@/logger' import shallowEqual from 'shallowequal' import useResizeObserver from '@/util/use-resize-observer.desktop' import useIntersectionObserver from '@/util/use-intersection-observer' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' // Infinite scrolling list. // We group messages into a series of Waypoints. When the waypoint exits the screen we replace it with a single div instead @@ -506,7 +506,7 @@ const ThreadWrapper = React.memo(function ThreadWrapper() { ) const {conversationIDKey, editingOrdinal, centeredOrdinal} = data const {containsLatestMessage, messageOrdinals, loaded, messageTypeMap} = data - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const listRef = React.useRef(null) const _setListRef = React.useCallback((r: HTMLDivElement | null) => { listRef.current = r diff --git a/shared/chat/conversation/list-area/index.native.tsx b/shared/chat/conversation/list-area/index.native.tsx index 129406200774..4a4ea8308087 100644 --- a/shared/chat/conversation/list-area/index.native.tsx +++ b/shared/chat/conversation/list-area/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Hooks from './hooks' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/load-status.tsx b/shared/chat/conversation/load-status.tsx index e992dd920145..786061c50ab9 100644 --- a/shared/chat/conversation/load-status.tsx +++ b/shared/chat/conversation/load-status.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/account-payment/container.tsx b/shared/chat/conversation/messages/account-payment/container.tsx index 7aec89fb2655..e728ab2606eb 100644 --- a/shared/chat/conversation/messages/account-payment/container.tsx +++ b/shared/chat/conversation/messages/account-payment/container.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import MarkdownMemo from '@/wallets/markdown-memo' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' // Props for rendering the loading indicator const loadingProps = { diff --git a/shared/chat/conversation/messages/account-payment/wrapper.tsx b/shared/chat/conversation/messages/account-payment/wrapper.tsx index 7e2bd8bf7b79..619ed8222849 100644 --- a/shared/chat/conversation/messages/account-payment/wrapper.tsx +++ b/shared/chat/conversation/messages/account-payment/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type PaymentMessageType from './container' diff --git a/shared/chat/conversation/messages/attachment/audio.tsx b/shared/chat/conversation/messages/attachment/audio.tsx index eb299725bc45..cd32586199c4 100644 --- a/shared/chat/conversation/messages/attachment/audio.tsx +++ b/shared/chat/conversation/messages/attachment/audio.tsx @@ -1,9 +1,9 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {useOrdinal} from '../ids-context' import AudioPlayer from '@/chat/audio/audio-player' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const missingMessage = Chat.makeMessageAttachment() @@ -25,7 +25,7 @@ const AudioAttachment = () => { const progressLabel = Chat.messageAttachmentTransferStateToProgressLabel(message.transferState) const hasProgress = messageAttachmentHasProgress(message) const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = () => { message.downloadPath && openLocalPathInSystemFileManagerDesktop?.(message.downloadPath) diff --git a/shared/chat/conversation/messages/attachment/file.tsx b/shared/chat/conversation/messages/attachment/file.tsx index 4d1dc8419ee6..bfcbe3bbc11f 100644 --- a/shared/chat/conversation/messages/attachment/file.tsx +++ b/shared/chat/conversation/messages/attachment/file.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Crypto from '@/constants/crypto' +import * as Chat from '@/stores/chat2' +import * as Crypto from '@/stores/crypto' import * as React from 'react' import {isPathSaltpack, isPathSaltpackEncrypted, isPathSaltpackSigned} from '@/util/path' import type * as T from '@/constants/types' @@ -9,7 +9,7 @@ import captialize from 'lodash/capitalize' import * as Kb from '@/common-adapters' import type {StyleOverride} from '@/common-adapters/markdown' import {getEditStyle, ShowToastAfterSaving} from './shared' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = {showPopup: () => void} @@ -57,7 +57,7 @@ const FileContainer = React.memo(function FileContainer(p: OwnProps) { [switchTab, saltpackOpenFile] ) const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const _onShowInFinder = React.useCallback(() => { downloadPath && openLocalPathInSystemFileManagerDesktop?.(downloadPath) diff --git a/shared/chat/conversation/messages/attachment/image2/use-state.tsx b/shared/chat/conversation/messages/attachment/image2/use-state.tsx index da4c4093b538..2399086be164 100644 --- a/shared/chat/conversation/messages/attachment/image2/use-state.tsx +++ b/shared/chat/conversation/messages/attachment/image2/use-state.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {useOrdinal} from '@/chat/conversation/messages/ids-context' import {maxWidth, maxHeight} from '../shared' diff --git a/shared/chat/conversation/messages/attachment/shared.tsx b/shared/chat/conversation/messages/attachment/shared.tsx index 379a85ae8374..3f0efebc0588 100644 --- a/shared/chat/conversation/messages/attachment/shared.tsx +++ b/shared/chat/conversation/messages/attachment/shared.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import {useOrdinal} from '../ids-context' import {sharedStyles} from '../shared-styles' import {Keyboard} from 'react-native' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = { transferState: T.Chat.MessageAttachmentTransferState @@ -103,7 +103,7 @@ export const TransferIcon = (p: {style: Kb.Styles.StylesCrossPlatform}) => { download(ordinal) }, [ordinal, download]) - const openFinder = useFSState(s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop) + const openFinder = useFSState(s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop) const onFinder = React.useCallback(() => { downloadPath && openFinder?.(downloadPath) }, [openFinder, downloadPath]) diff --git a/shared/chat/conversation/messages/attachment/video/use-state.tsx b/shared/chat/conversation/messages/attachment/video/use-state.tsx index b02adc6d8a21..5fc3c9eaa684 100644 --- a/shared/chat/conversation/messages/attachment/video/use-state.tsx +++ b/shared/chat/conversation/messages/attachment/video/use-state.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {useOrdinal} from '@/chat/conversation/messages/ids-context' import {missingMessage, maxWidth, maxHeight} from '../shared' diff --git a/shared/chat/conversation/messages/cards/make-team.tsx b/shared/chat/conversation/messages/cards/make-team.tsx index ce3ca6ea16b3..d01d46e8e954 100644 --- a/shared/chat/conversation/messages/cards/make-team.tsx +++ b/shared/chat/conversation/messages/cards/make-team.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' const MakeTeam = () => { diff --git a/shared/chat/conversation/messages/cards/team-journey/container.tsx b/shared/chat/conversation/messages/cards/team-journey/container.tsx index 249383bf41a7..19d47a2be434 100644 --- a/shared/chat/conversation/messages/cards/team-journey/container.tsx +++ b/shared/chat/conversation/messages/cards/team-journey/container.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import {renderWelcomeMessage} from './util' import {useAllChannelMetas} from '@/teams/common/channel-hooks' diff --git a/shared/chat/conversation/messages/emoji-row.tsx b/shared/chat/conversation/messages/emoji-row.tsx index 43e13a7f324c..ffbb461d8082 100644 --- a/shared/chat/conversation/messages/emoji-row.tsx +++ b/shared/chat/conversation/messages/emoji-row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {useOrdinal} from './ids-context' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/message-popup/attachment.tsx b/shared/chat/conversation/messages/message-popup/attachment.tsx index 0cf6fdb9efb9..b6da989e6deb 100644 --- a/shared/chat/conversation/messages/message-popup/attachment.tsx +++ b/shared/chat/conversation/messages/message-popup/attachment.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import {type Position, fileUIName, type StylesCrossPlatform} from '@/styles' import {useItems, useHeader} from './hooks' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { attachTo?: React.RefObject @@ -84,7 +84,7 @@ const PopAttach = (ownProps: OwnProps) => { const onShareAttachment = C.isMobile ? _onShareAttachment : undefined const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const _onShowInFinder = React.useCallback(() => { downloadPath && openLocalPathInSystemFileManagerDesktop?.(downloadPath) diff --git a/shared/chat/conversation/messages/message-popup/exploding-header.tsx b/shared/chat/conversation/messages/message-popup/exploding-header.tsx index e390b96f7879..7d691f4b30b8 100644 --- a/shared/chat/conversation/messages/message-popup/exploding-header.tsx +++ b/shared/chat/conversation/messages/message-popup/exploding-header.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import {formatTimeForPopup, formatTimeForRevoked, msToDHMS} from '@/util/timestamp' import {addTicker, removeTicker} from '@/util/second-timer' diff --git a/shared/chat/conversation/messages/message-popup/header.tsx b/shared/chat/conversation/messages/message-popup/header.tsx index d0221144273a..d57e76ad84f5 100644 --- a/shared/chat/conversation/messages/message-popup/header.tsx +++ b/shared/chat/conversation/messages/message-popup/header.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as React from 'react' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import {formatTimeForPopup, formatTimeForRevoked} from '@/util/timestamp' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/message-popup/hooks.tsx b/shared/chat/conversation/messages/message-popup/hooks.tsx index 6a8a35db164b..48b40ada8b7f 100644 --- a/shared/chat/conversation/messages/message-popup/hooks.tsx +++ b/shared/chat/conversation/messages/message-popup/hooks.tsx @@ -1,11 +1,11 @@ import * as React from 'react' import type * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' -import {useConfigState} from '@/constants/config' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' +import {useConfigState} from '@/stores/config' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' import {linkFromConvAndMessage} from '@/constants/deeplinks' import ReactionItem from './reactionitem' import MessagePopupHeader from './header' @@ -104,7 +104,7 @@ export const useItems = (ordinal: T.Chat.Ordinal, onHidden: () => void) => { : [] const convLabel = getConversationLabel(participantInfo, meta, true) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopyLink = React.useCallback(() => { copyToClipboard(linkFromConvAndMessage(convLabel, id)) }, [copyToClipboard, id, convLabel]) diff --git a/shared/chat/conversation/messages/message-popup/index.tsx b/shared/chat/conversation/messages/message-popup/index.tsx index 111c936d3678..3736bfdc3587 100644 --- a/shared/chat/conversation/messages/message-popup/index.tsx +++ b/shared/chat/conversation/messages/message-popup/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import AttachmentMessage from './attachment' diff --git a/shared/chat/conversation/messages/message-popup/journeycard.tsx b/shared/chat/conversation/messages/message-popup/journeycard.tsx index 560590fd8ce3..f631f84246f6 100644 --- a/shared/chat/conversation/messages/message-popup/journeycard.tsx +++ b/shared/chat/conversation/messages/message-popup/journeycard.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/chat/conversation/messages/message-popup/reactionitem.tsx b/shared/chat/conversation/messages/message-popup/reactionitem.tsx index 051dce87b10c..9c734e3f529c 100644 --- a/shared/chat/conversation/messages/message-popup/reactionitem.tsx +++ b/shared/chat/conversation/messages/message-popup/reactionitem.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' type Props = { diff --git a/shared/chat/conversation/messages/message-popup/text.tsx b/shared/chat/conversation/messages/message-popup/text.tsx index 3a8c7f28077d..66425418691d 100644 --- a/shared/chat/conversation/messages/message-popup/text.tsx +++ b/shared/chat/conversation/messages/message-popup/text.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {Position, StylesCrossPlatform} from '@/styles' import {useItems, useHeader} from './hooks' import openURL from '@/util/open-url' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { attachTo?: React.RefObject @@ -75,7 +75,7 @@ const PopText = (ownProps: OwnProps) => { // you can reply privately *if* text message, someone else's message, and not in a 1-on-1 chat const canReplyPrivately = ['small', 'big'].includes(teamType) || numPart > 2 const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopy = React.useCallback(() => { text && copyToClipboard(text) }, [copyToClipboard, text]) diff --git a/shared/chat/conversation/messages/pin/index.tsx b/shared/chat/conversation/messages/pin/index.tsx index 937d4cb6fb2c..7618510b7557 100644 --- a/shared/chat/conversation/messages/pin/index.tsx +++ b/shared/chat/conversation/messages/pin/index.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/pin/wrapper.tsx b/shared/chat/conversation/messages/pin/wrapper.tsx index 5cfd3cb7c7f2..67125e1e8575 100644 --- a/shared/chat/conversation/messages/pin/wrapper.tsx +++ b/shared/chat/conversation/messages/pin/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type PinType from '.' diff --git a/shared/chat/conversation/messages/placeholder/wrapper.tsx b/shared/chat/conversation/messages/placeholder/wrapper.tsx index 5cc050f9d681..45c196e3be7a 100644 --- a/shared/chat/conversation/messages/placeholder/wrapper.tsx +++ b/shared/chat/conversation/messages/placeholder/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/react-button.tsx b/shared/chat/conversation/messages/react-button.tsx index 2486bd7a1129..4ce79b009d4b 100644 --- a/shared/chat/conversation/messages/react-button.tsx +++ b/shared/chat/conversation/messages/react-button.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type {StylesCrossPlatform} from '@/styles' import {useOrdinal} from './ids-context' @@ -7,7 +7,7 @@ import * as Kb from '@/common-adapters' import type {StyleOverride} from '@/common-adapters/markdown' import {colors, darkColors} from '@/styles/colors' import {useColorScheme} from 'react-native' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { className?: string diff --git a/shared/chat/conversation/messages/reaction-tooltip.tsx b/shared/chat/conversation/messages/reaction-tooltip.tsx index 985f3edacb4f..d196fa1180f1 100644 --- a/shared/chat/conversation/messages/reaction-tooltip.tsx +++ b/shared/chat/conversation/messages/reaction-tooltip.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import ReactButton from './react-button' import type * as T from '@/constants/types' import {MessageContext} from './ids-context' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' const positionFallbacks = ['bottom center', 'left center'] as const diff --git a/shared/chat/conversation/messages/reactions-rows.tsx b/shared/chat/conversation/messages/reactions-rows.tsx index cde5d850f470..21d1eef42325 100644 --- a/shared/chat/conversation/messages/reactions-rows.tsx +++ b/shared/chat/conversation/messages/reactions-rows.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import EmojiRow from './emoji-row' diff --git a/shared/chat/conversation/messages/reset-user.tsx b/shared/chat/conversation/messages/reset-user.tsx index 08ae473cf8ff..5ef6bde4ba85 100644 --- a/shared/chat/conversation/messages/reset-user.tsx +++ b/shared/chat/conversation/messages/reset-user.tsx @@ -1,5 +1,5 @@ -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' const ResetUser = () => { diff --git a/shared/chat/conversation/messages/retention-notice.tsx b/shared/chat/conversation/messages/retention-notice.tsx index add6faf305ea..77327c4f58ed 100644 --- a/shared/chat/conversation/messages/retention-notice.tsx +++ b/shared/chat/conversation/messages/retention-notice.tsx @@ -1,6 +1,6 @@ import type * as T from '@/constants/types' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/messages/separator.tsx b/shared/chat/conversation/messages/separator.tsx index d2a640007553..877beff3ad3a 100644 --- a/shared/chat/conversation/messages/separator.tsx +++ b/shared/chat/conversation/messages/separator.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import {formatTimeForConversationList, formatTimeForChat} from '@/util/timestamp' import {OrangeLineContext} from '../orange-line-context' import logger from '@/logger' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' // import {useChatDebugDump} from '@/constants/chat2/debug' const enoughTimeBetweenMessages = (mtimestamp?: number, ptimestamp?: number): boolean => diff --git a/shared/chat/conversation/messages/set-channelname/wrapper.tsx b/shared/chat/conversation/messages/set-channelname/wrapper.tsx index e3a0c0164c7d..c507ae29e940 100644 --- a/shared/chat/conversation/messages/set-channelname/wrapper.tsx +++ b/shared/chat/conversation/messages/set-channelname/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SetChannelnameType from './container' diff --git a/shared/chat/conversation/messages/set-description/wrapper.tsx b/shared/chat/conversation/messages/set-description/wrapper.tsx index 9f8400a9e58d..0383a7edc43e 100644 --- a/shared/chat/conversation/messages/set-description/wrapper.tsx +++ b/shared/chat/conversation/messages/set-description/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SetDescriptionType from './container' diff --git a/shared/chat/conversation/messages/special-bottom-message.tsx b/shared/chat/conversation/messages/special-bottom-message.tsx index e6e45bf35bee..386a56528d86 100644 --- a/shared/chat/conversation/messages/special-bottom-message.tsx +++ b/shared/chat/conversation/messages/special-bottom-message.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import OldProfileReset from './system-old-profile-reset-notice/container' import ResetUser from './reset-user' diff --git a/shared/chat/conversation/messages/special-top-message.tsx b/shared/chat/conversation/messages/special-top-message.tsx index 40205b7dc910..e726d608a19c 100644 --- a/shared/chat/conversation/messages/special-top-message.tsx +++ b/shared/chat/conversation/messages/special-top-message.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' @@ -11,7 +11,7 @@ import ProfileResetNotice from './system-profile-reset-notice' import RetentionNotice from './retention-notice' import {usingFlashList} from '../list-area/flashlist-config' import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const ErrorMessage = () => { const createConversationError = Chat.useChatState(s => s.createConversationError) @@ -178,7 +178,7 @@ const SpecialTopMessage = React.memo(function SpecialTopMessage() { }, []) const openPrivateFolder = React.useCallback(() => { - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/private/${username}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/private/${username}`)) }, [username]) return ( diff --git a/shared/chat/conversation/messages/system-added-to-team/container.tsx b/shared/chat/conversation/messages/system-added-to-team/container.tsx index 58c6e70e030f..10999c12dc9f 100644 --- a/shared/chat/conversation/messages/system-added-to-team/container.tsx +++ b/shared/chat/conversation/messages/system-added-to-team/container.tsx @@ -1,12 +1,12 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' import {getAddedUsernames} from '../system-users-added-to-conv/container' import {indefiniteArticle} from '@/util/string' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemAddedToTeam} diff --git a/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx b/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx index f90914a28f97..dcefb1626e38 100644 --- a/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx +++ b/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemAddedToTeamType from './container' diff --git a/shared/chat/conversation/messages/system-change-avatar/index.tsx b/shared/chat/conversation/messages/system-change-avatar/index.tsx index bdf0fffdfe4b..8aa32c6b3098 100644 --- a/shared/chat/conversation/messages/system-change-avatar/index.tsx +++ b/shared/chat/conversation/messages/system-change-avatar/index.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { message: T.Chat.MessageSystemChangeAvatar diff --git a/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx b/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx index 172ac0051c55..ed14bda12cb3 100644 --- a/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx +++ b/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemChangeAvatarType from '.' diff --git a/shared/chat/conversation/messages/system-change-retention/container.tsx b/shared/chat/conversation/messages/system-change-retention/container.tsx index c0ad42530fa3..d192ebd93f88 100644 --- a/shared/chat/conversation/messages/system-change-retention/container.tsx +++ b/shared/chat/conversation/messages/system-change-retention/container.tsx @@ -1,11 +1,11 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' import * as T from '@/constants/types' import * as dateFns from 'date-fns' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemChangeRetention} diff --git a/shared/chat/conversation/messages/system-change-retention/wrapper.tsx b/shared/chat/conversation/messages/system-change-retention/wrapper.tsx index 85490ad934a3..8d6d231f78ed 100644 --- a/shared/chat/conversation/messages/system-change-retention/wrapper.tsx +++ b/shared/chat/conversation/messages/system-change-retention/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemChangeRetentionType from './container' diff --git a/shared/chat/conversation/messages/system-create-team/container.tsx b/shared/chat/conversation/messages/system-create-team/container.tsx index f248de32f0f6..b3c654e66d2f 100644 --- a/shared/chat/conversation/messages/system-create-team/container.tsx +++ b/shared/chat/conversation/messages/system-create-team/container.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' import type * as T from '@/constants/types' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemCreateTeam} diff --git a/shared/chat/conversation/messages/system-create-team/wrapper.tsx b/shared/chat/conversation/messages/system-create-team/wrapper.tsx index 4d4cd246197a..2a9216ababec 100644 --- a/shared/chat/conversation/messages/system-create-team/wrapper.tsx +++ b/shared/chat/conversation/messages/system-create-team/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemCreateTeamType from './container' diff --git a/shared/chat/conversation/messages/system-git-push/container.tsx b/shared/chat/conversation/messages/system-git-push/container.tsx index 3d8d2c346a98..725af44edf1a 100644 --- a/shared/chat/conversation/messages/system-git-push/container.tsx +++ b/shared/chat/conversation/messages/system-git-push/container.tsx @@ -1,11 +1,11 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useGitState} from '@/constants/git' +import {useGitState} from '@/stores/git' import UserNotice from '../user-notice' import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemGitPush} @@ -21,7 +21,7 @@ const GitContainer = React.memo(function GitContainer(p: OwnProps) { '/.kbfs_autogit_commit_' + commitHash ) - FS.makeActionForOpenPathInFilesTab(path) + FS.navToPath(path) }, [message] ) diff --git a/shared/chat/conversation/messages/system-git-push/wrapper.tsx b/shared/chat/conversation/messages/system-git-push/wrapper.tsx index aaac3ac464e6..89e05a0090f8 100644 --- a/shared/chat/conversation/messages/system-git-push/wrapper.tsx +++ b/shared/chat/conversation/messages/system-git-push/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemGitPushType from './container' diff --git a/shared/chat/conversation/messages/system-invite-accepted/container.tsx b/shared/chat/conversation/messages/system-invite-accepted/container.tsx index b3b76a752bba..b2d1357a85fd 100644 --- a/shared/chat/conversation/messages/system-invite-accepted/container.tsx +++ b/shared/chat/conversation/messages/system-invite-accepted/container.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemInviteAccepted} diff --git a/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx b/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx index 118224348648..aeed739b80d4 100644 --- a/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx +++ b/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemInviteAcceptedType from './container' diff --git a/shared/chat/conversation/messages/system-joined/container.tsx b/shared/chat/conversation/messages/system-joined/container.tsx index 7ca9df41d333..a9dc7563c9d2 100644 --- a/shared/chat/conversation/messages/system-joined/container.tsx +++ b/shared/chat/conversation/messages/system-joined/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/system-joined/wrapper.tsx b/shared/chat/conversation/messages/system-joined/wrapper.tsx index 996f2dc95968..7fe46e21979f 100644 --- a/shared/chat/conversation/messages/system-joined/wrapper.tsx +++ b/shared/chat/conversation/messages/system-joined/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemJoinedType from './container' diff --git a/shared/chat/conversation/messages/system-left/container.tsx b/shared/chat/conversation/messages/system-left/container.tsx index 70ed9a1b3d74..65d0cdf61516 100644 --- a/shared/chat/conversation/messages/system-left/container.tsx +++ b/shared/chat/conversation/messages/system-left/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' diff --git a/shared/chat/conversation/messages/system-left/wrapper.tsx b/shared/chat/conversation/messages/system-left/wrapper.tsx index cf6f97dea1dd..53f8df6c82d2 100644 --- a/shared/chat/conversation/messages/system-left/wrapper.tsx +++ b/shared/chat/conversation/messages/system-left/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemLeftType from './container' diff --git a/shared/chat/conversation/messages/system-new-channel/container.tsx b/shared/chat/conversation/messages/system-new-channel/container.tsx index fb791f2b70b4..f8e1e201837d 100644 --- a/shared/chat/conversation/messages/system-new-channel/container.tsx +++ b/shared/chat/conversation/messages/system-new-channel/container.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import UserNotice from '../user-notice' diff --git a/shared/chat/conversation/messages/system-new-channel/wrapper.tsx b/shared/chat/conversation/messages/system-new-channel/wrapper.tsx index 27da6aaba425..be12f9f0554f 100644 --- a/shared/chat/conversation/messages/system-new-channel/wrapper.tsx +++ b/shared/chat/conversation/messages/system-new-channel/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemNewChannelType from './container' diff --git a/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx b/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx index 4044fd8a281b..e306630f87ad 100644 --- a/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx +++ b/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import {Text} from '@/common-adapters' import UserNotice from '../user-notice' diff --git a/shared/chat/conversation/messages/system-profile-reset-notice.tsx b/shared/chat/conversation/messages/system-profile-reset-notice.tsx index 2d7deca69d72..0a2677a6140d 100644 --- a/shared/chat/conversation/messages/system-profile-reset-notice.tsx +++ b/shared/chat/conversation/messages/system-profile-reset-notice.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from './user-notice' diff --git a/shared/chat/conversation/messages/system-sbs-resolve/container.tsx b/shared/chat/conversation/messages/system-sbs-resolve/container.tsx index 51da16607892..8346cd4d5a80 100644 --- a/shared/chat/conversation/messages/system-sbs-resolve/container.tsx +++ b/shared/chat/conversation/messages/system-sbs-resolve/container.tsx @@ -2,7 +2,7 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {e164ToDisplay} from '@/util/phone-numbers' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemSBSResolved} diff --git a/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx b/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx index 32db70666c32..bd9d9626c95a 100644 --- a/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx +++ b/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx @@ -1,9 +1,9 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemSBSResolvedType from './container' import type SystemJoinedType from '../system-joined/container' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const WrapperSystemInvite = React.memo(function WrapperSystemInvite(p: Props) { const {ordinal} = p diff --git a/shared/chat/conversation/messages/system-simple-to-complex/container.tsx b/shared/chat/conversation/messages/system-simple-to-complex/container.tsx index d0066afafefb..baa73a2f6af5 100644 --- a/shared/chat/conversation/messages/system-simple-to-complex/container.tsx +++ b/shared/chat/conversation/messages/system-simple-to-complex/container.tsx @@ -1,10 +1,10 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemSimpleToComplex} diff --git a/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx b/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx index 93d830470578..d894327e7599 100644 --- a/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx +++ b/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemSimpleToComplexType from './container' diff --git a/shared/chat/conversation/messages/system-text/wrapper.tsx b/shared/chat/conversation/messages/system-text/wrapper.tsx index 9b99bc02b180..4f2efa4555f6 100644 --- a/shared/chat/conversation/messages/system-text/wrapper.tsx +++ b/shared/chat/conversation/messages/system-text/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemTextType from './container' diff --git a/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx b/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx index 3d67beba56bb..1a9d48e4233d 100644 --- a/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx +++ b/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx @@ -1,9 +1,9 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemUsersAddedToConversation} diff --git a/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx b/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx index 5941d8eebbcd..1fe3e0bf1c8b 100644 --- a/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx +++ b/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemUsersAddedToConvType from './container' diff --git a/shared/chat/conversation/messages/text/bottom.tsx b/shared/chat/conversation/messages/text/bottom.tsx index 128cc464ef16..e1a26112b6ab 100644 --- a/shared/chat/conversation/messages/text/bottom.tsx +++ b/shared/chat/conversation/messages/text/bottom.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import type CoinFlipType from './coinflip' diff --git a/shared/chat/conversation/messages/text/coinflip/index.tsx b/shared/chat/conversation/messages/text/coinflip/index.tsx index 223547f15130..45f53d2d088d 100644 --- a/shared/chat/conversation/messages/text/coinflip/index.tsx +++ b/shared/chat/conversation/messages/text/coinflip/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/text/reply.tsx b/shared/chat/conversation/messages/text/reply.tsx index 7ad7cfacd4f9..bc0d9c745128 100644 --- a/shared/chat/conversation/messages/text/reply.tsx +++ b/shared/chat/conversation/messages/text/reply.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import {useOrdinal, useIsHighlighted} from '../ids-context' diff --git a/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx b/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx index 240d090eab40..ea0da57b5081 100644 --- a/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx +++ b/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {useOrdinal} from '@/chat/conversation/messages/ids-context' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx index 444f2452a61e..51105368b6e1 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx index 3e97b16ef8fd..238515cda7c3 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import * as React from 'react' import UnfurlImage from './image' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx index a826e6dc5e04..e2a3ba2f2c5d 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Kb from '@/common-adapters/index' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {maxWidth} from '@/chat/conversation/messages/attachment/shared' import {Video} from './video' import openURL from '@/util/open-url' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx index 1aa89030311a..65c37cb40387 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as React from 'react' import UnfurlGeneric from './generic' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx index 243034849db6..59e03f3db1c5 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import type * as T from '@/constants/types' import openURL from '@/util/open-url' import LocationMap from '@/chat/location-map' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' type Props = { coord: T.Chat.Coordinate diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx index 2cf754f6891e..68ec76e60397 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx index 4a7fddb9d9dd..1092daf24b90 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx @@ -1,7 +1,7 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' export const useActions = (youAreAuthor: boolean, messageID: T.Chat.MessageID, ordinal: T.Chat.Ordinal) => { const unfurlRemove = Chat.useChatContext(s => s.dispatch.unfurlRemove) diff --git a/shared/chat/conversation/messages/text/wrapper.tsx b/shared/chat/conversation/messages/text/wrapper.tsx index d3a6ed554bae..c1b077922b4b 100644 --- a/shared/chat/conversation/messages/text/wrapper.tsx +++ b/shared/chat/conversation/messages/text/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import {useReply} from './reply' diff --git a/shared/chat/conversation/messages/wrapper/edited.tsx b/shared/chat/conversation/messages/wrapper/edited.tsx index bc39cbae643b..a3e2d0df2fbe 100644 --- a/shared/chat/conversation/messages/wrapper/edited.tsx +++ b/shared/chat/conversation/messages/wrapper/edited.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {useIsHighlighted, useOrdinal} from '../ids-context' diff --git a/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx b/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx index fae3926d1218..0fc71930593c 100644 --- a/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx +++ b/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import ExplodingHeightRetainer from '.' import {useOrdinal} from '../../ids-context' diff --git a/shared/chat/conversation/messages/wrapper/exploding-meta.tsx b/shared/chat/conversation/messages/wrapper/exploding-meta.tsx index c6a3a6c1bdde..fa796f6b0354 100644 --- a/shared/chat/conversation/messages/wrapper/exploding-meta.tsx +++ b/shared/chat/conversation/messages/wrapper/exploding-meta.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {useIsHighlighted, useOrdinal} from '../ids-context' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx b/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx index f5d163fce1c0..72f39ea87942 100644 --- a/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx +++ b/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import type {Props} from '.' diff --git a/shared/chat/conversation/messages/wrapper/send-indicator.tsx b/shared/chat/conversation/messages/wrapper/send-indicator.tsx index 4f5df715d762..6f197da1ed5a 100644 --- a/shared/chat/conversation/messages/wrapper/send-indicator.tsx +++ b/shared/chat/conversation/messages/wrapper/send-indicator.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {useOrdinal} from '../ids-context' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/wrapper/wrapper.tsx b/shared/chat/conversation/messages/wrapper/wrapper.tsx index d1c4186278d0..e4f0a9609e36 100644 --- a/shared/chat/conversation/messages/wrapper/wrapper.tsx +++ b/shared/chat/conversation/messages/wrapper/wrapper.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import {MessageContext, useOrdinal} from '../ids-context' @@ -13,7 +13,7 @@ import SendIndicator from './send-indicator' import * as T from '@/constants/types' import capitalize from 'lodash/capitalize' import {useEdited} from './edited' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' // import {useDebugLayout} from '@/util/debug-react' export type Props = { diff --git a/shared/chat/conversation/normal/container.tsx b/shared/chat/conversation/normal/container.tsx index fd38892d4b89..dc1ee30496aa 100644 --- a/shared/chat/conversation/normal/container.tsx +++ b/shared/chat/conversation/normal/container.tsx @@ -1,10 +1,9 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as React from 'react' import Normal from '.' import * as T from '@/constants/types' -import {useActiveState} from '@/constants/active' import {FocusProvider, ScrollProvider} from './context' import {OrangeLineContext} from '../orange-line-context' @@ -51,7 +50,7 @@ const useOrangeLine = () => { // just use the rpc for orange line if we're not active // if we are active we want to keep whatever state we had so it is maintained - const active = useActiveState(s => s.active) + const active = useConfigState(s => s.active) React.useEffect(() => { if (!active) { loadOrangeLine() diff --git a/shared/chat/conversation/normal/index.desktop.tsx b/shared/chat/conversation/normal/index.desktop.tsx index 645314cf0f43..2e5dde5320c0 100644 --- a/shared/chat/conversation/normal/index.desktop.tsx +++ b/shared/chat/conversation/normal/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import Banner from '../bottom-banner' diff --git a/shared/chat/conversation/normal/index.native.tsx b/shared/chat/conversation/normal/index.native.tsx index b51457ece826..bfb4263d9019 100644 --- a/shared/chat/conversation/normal/index.native.tsx +++ b/shared/chat/conversation/normal/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {PortalHost} from '@/common-adapters/portal.native' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/pinned-message.tsx b/shared/chat/conversation/pinned-message.tsx index c1bee40e5890..9e18af274b21 100644 --- a/shared/chat/conversation/pinned-message.tsx +++ b/shared/chat/conversation/pinned-message.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const PinnedMessage = React.memo(function PinnedMessage() { const {conversationIDKey, teamname, pinnedMsg, replyJump, onIgnore, pinMessage} = Chat.useChatContext( diff --git a/shared/chat/conversation/rekey/container.tsx b/shared/chat/conversation/rekey/container.tsx index d4397fb0346a..8cc2428b7f7b 100644 --- a/shared/chat/conversation/rekey/container.tsx +++ b/shared/chat/conversation/rekey/container.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' import * as T from '@/constants/types' import ParticipantRekey from './participant-rekey' import YouRekey from './you-rekey' diff --git a/shared/chat/conversation/reply-preview.tsx b/shared/chat/conversation/reply-preview.tsx index 4997bad338bd..b194fb2091cb 100644 --- a/shared/chat/conversation/reply-preview.tsx +++ b/shared/chat/conversation/reply-preview.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/search.tsx b/shared/chat/conversation/search.tsx index 20d9d78fa081..455b96ee6743 100644 --- a/shared/chat/conversation/search.tsx +++ b/shared/chat/conversation/search.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as Styles from '@/styles' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/create-channel/hooks.tsx b/shared/chat/create-channel/hooks.tsx index ec52505df5e5..ae69f3d75443 100644 --- a/shared/chat/create-channel/hooks.tsx +++ b/shared/chat/create-channel/hooks.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import upperFirst from 'lodash/upperFirst' import type {Props} from '.' diff --git a/shared/chat/delete-history-warning.tsx b/shared/chat/delete-history-warning.tsx index 1e748facdc92..47ffafd71431 100644 --- a/shared/chat/delete-history-warning.tsx +++ b/shared/chat/delete-history-warning.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import MaybePopup from './maybe-popup' const DeleteHistoryWarning = () => { diff --git a/shared/chat/emoji-picker/container.tsx b/shared/chat/emoji-picker/container.tsx index abe1b741f551..34376fe70b98 100644 --- a/shared/chat/emoji-picker/container.tsx +++ b/shared/chat/emoji-picker/container.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/inbox-and-conversation-2.tsx b/shared/chat/inbox-and-conversation-2.tsx index 730766b62d5b..f0a13e01420a 100644 --- a/shared/chat/inbox-and-conversation-2.tsx +++ b/shared/chat/inbox-and-conversation-2.tsx @@ -1,6 +1,6 @@ // Just for desktop and tablet, we show inbox and conversation side by side import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' diff --git a/shared/chat/inbox-and-conversation-header.tsx b/shared/chat/inbox-and-conversation-header.tsx index 157b61f73d3b..46fba91a82b5 100644 --- a/shared/chat/inbox-and-conversation-header.tsx +++ b/shared/chat/inbox-and-conversation-header.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type {StyleOverride} from '@/common-adapters/markdown' @@ -7,9 +7,9 @@ import SearchRow from './inbox/search-row' import NewChatButton from './inbox/new-chat-button' import {useRoute} from '@react-navigation/native' import type {RootRouteProps} from '@/router-v2/route-params' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' -import * as Teams from '@/constants/teams' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' +import * as Teams from '@/stores/teams' const Header = () => { const {params} = useRoute>() diff --git a/shared/chat/inbox-search/index.tsx b/shared/chat/inbox-search/index.tsx index f85ab2edb2d3..823e2b970a33 100644 --- a/shared/chat/inbox-search/index.tsx +++ b/shared/chat/inbox-search/index.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import Rover from './background' diff --git a/shared/chat/inbox/container.tsx b/shared/chat/inbox/container.tsx index a8f303cc416c..1bd795aef1d2 100644 --- a/shared/chat/inbox/container.tsx +++ b/shared/chat/inbox/container.tsx @@ -1,9 +1,17 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as T from '@/constants/types' import Inbox, {type Props} from '.' import {useIsFocused} from '@react-navigation/core' +import type { + ChatInboxRowItemBig, + ChatInboxRowItemBigHeader, + ChatInboxRowItemTeamBuilder, + ChatInboxRowItemSmall, + ChatInboxRowItemDivider, + ChatInboxRowItem, +} from './rowitem' type OwnProps = { navKey: string @@ -12,9 +20,7 @@ type OwnProps = { const makeBigRows = ( bigTeams: ReadonlyArray -): Array< - T.Chat.ChatInboxRowItemBig | T.Chat.ChatInboxRowItemBigHeader | T.Chat.ChatInboxRowItemTeamBuilder -> => { +): Array => { return bigTeams.map(t => { switch (t.state) { case T.RPCChat.UIInboxBigTeamRowTyp.channel: { @@ -43,7 +49,7 @@ const makeBigRows = ( const makeSmallRows = ( smallTeams: ReadonlyArray -): Array => { +): Array => { return smallTeams.map(t => { const conversationIDKey = T.Chat.stringToConversationIDKey(t.convID) return { @@ -180,18 +186,17 @@ const Connected = (ownProps: OwnProps) => { const hasAllSmallTeamConvs = (_inboxLayout?.smallTeams?.length ?? 0) === (_inboxLayout?.totalSmallTeams ?? 0) - const divider: Array = - React.useMemo(() => { - return bigRows.length !== 0 || !hasAllSmallTeamConvs - ? [{showButton: !hasAllSmallTeamConvs || smallTeamsBelowTheFold, type: 'divider'}] - : [] - }, [bigRows.length, hasAllSmallTeamConvs, smallTeamsBelowTheFold]) - - const rows: Array = React.useMemo(() => { - const builderAfterSmall = new Array() - const builderAfterDivider = new Array() - const builderAfterBig = new Array() - const teamBuilder: T.Chat.ChatInboxRowItemTeamBuilder = {type: 'teamBuilder'} + const divider: Array = React.useMemo(() => { + return bigRows.length !== 0 || !hasAllSmallTeamConvs + ? [{showButton: !hasAllSmallTeamConvs || smallTeamsBelowTheFold, type: 'divider'}] + : [] + }, [bigRows.length, hasAllSmallTeamConvs, smallTeamsBelowTheFold]) + + const rows: Array = React.useMemo(() => { + const builderAfterSmall = new Array() + const builderAfterDivider = new Array() + const builderAfterBig = new Array() + const teamBuilder: ChatInboxRowItemTeamBuilder = {type: 'teamBuilder'} if (smallRows.length !== 0) { if (bigRows.length === 0) { if (divider.length !== 0) { diff --git a/shared/chat/inbox/filter-row.tsx b/shared/chat/inbox/filter-row.tsx index 8a5651be6445..1d7214f8b4c1 100644 --- a/shared/chat/inbox/filter-row.tsx +++ b/shared/chat/inbox/filter-row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/inbox/index.d.ts b/shared/chat/inbox/index.d.ts index 71bf8d45eba2..24737a0fdf77 100644 --- a/shared/chat/inbox/index.d.ts +++ b/shared/chat/inbox/index.d.ts @@ -1,5 +1,6 @@ import type * as React from 'react' import type * as T from '@/constants/types' +import type {ChatInboxRowItem} from './rowitem' export type Props = { allowShowFloatingButton: boolean @@ -10,7 +11,7 @@ export type Props = { neverLoaded: boolean onNewChat: () => void onUntrustedInboxVisible: (conversationIDKeys: Array) => void - rows: Array + rows: Array setInboxNumSmallRows: (rows: number) => void smallTeamsExpanded: boolean toggleSmallTeamsExpanded: () => void diff --git a/shared/chat/inbox/index.desktop.tsx b/shared/chat/inbox/index.desktop.tsx index 98eac06219af..c40b4875c1ea 100644 --- a/shared/chat/inbox/index.desktop.tsx +++ b/shared/chat/inbox/index.desktop.tsx @@ -4,6 +4,7 @@ import * as C from '@/constants' import * as React from 'react' import type * as TInbox from './index.d' import type * as T from '@/constants/types' +import type {ChatInboxRowItem} from './rowitem' import BigTeamsDivider from './row/big-teams-divider' import BuildTeam from './row/build-team' import TeamsDivider from './row/teams-divider' @@ -47,7 +48,7 @@ const DragLine = (p: { toggleSmallTeamsExpanded: () => void setInboxNumSmallRows: (n: number) => void style: object - rows: T.Chat.ChatInboxRowItem[] + rows: ChatInboxRowItem[] }) => { const {inboxNumSmallRows, showButton, style, scrollDiv} = p const {smallTeamsExpanded, toggleSmallTeamsExpanded, rows, setInboxNumSmallRows} = p @@ -185,7 +186,7 @@ const DragLine = (p: { type InboxRowData = { inboxNumSmallRows: number navKey: string - rows: T.Chat.ChatInboxRowItem[] + rows: ChatInboxRowItem[] scrollDiv: React.RefObject selectedConversationIDKey: string setInboxNumSmallRows: (rows: number) => void diff --git a/shared/chat/inbox/index.native.tsx b/shared/chat/inbox/index.native.tsx index 3a33bbcbe653..e1aa6aaf9ceb 100644 --- a/shared/chat/inbox/index.native.tsx +++ b/shared/chat/inbox/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as RowSizes from './row/sizes' @@ -16,8 +16,9 @@ import {FlatList} from 'react-native-gesture-handler' // import {FlashList, type ListRenderItemInfo} from '@shopify/flash-list' import {makeRow} from './row' import {useOpenedRowState} from './row/opened-row-state' +import type {ChatInboxRowItem} from './rowitem' -type RowItem = T.Chat.ChatInboxRowItem +type RowItem = ChatInboxRowItem const usingFlashList = false as boolean const List = /*usingFlashList ? FlashList :*/ FlatList diff --git a/shared/chat/inbox/new-chat-button.tsx b/shared/chat/inbox/new-chat-button.tsx index e6987c1ddc92..bb086547ac38 100644 --- a/shared/chat/inbox/new-chat-button.tsx +++ b/shared/chat/inbox/new-chat-button.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/inbox/row/big-team-channel.tsx b/shared/chat/inbox/row/big-team-channel.tsx index 79a4e46f06f3..e4089507b3b3 100644 --- a/shared/chat/inbox/row/big-team-channel.tsx +++ b/shared/chat/inbox/row/big-team-channel.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as RowSizes from './sizes' diff --git a/shared/chat/inbox/row/big-team-header.tsx b/shared/chat/inbox/row/big-team-header.tsx index b4cf9085a9b0..0fc4172f6497 100644 --- a/shared/chat/inbox/row/big-team-header.tsx +++ b/shared/chat/inbox/row/big-team-header.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as RowSizes from './sizes' import type * as T from '@/constants/types' diff --git a/shared/chat/inbox/row/big-teams-divider.tsx b/shared/chat/inbox/row/big-teams-divider.tsx index 35fc6ab594b1..547f2e267670 100644 --- a/shared/chat/inbox/row/big-teams-divider.tsx +++ b/shared/chat/inbox/row/big-teams-divider.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as RowSizes from './sizes' diff --git a/shared/chat/inbox/row/build-team.tsx b/shared/chat/inbox/row/build-team.tsx index 7f6415928fed..822260af2b68 100644 --- a/shared/chat/inbox/row/build-team.tsx +++ b/shared/chat/inbox/row/build-team.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/chat/inbox/row/index.tsx b/shared/chat/inbox/row/index.tsx index 0cd35a8c9631..c47b53e3576c 100644 --- a/shared/chat/inbox/row/index.tsx +++ b/shared/chat/inbox/row/index.tsx @@ -3,9 +3,9 @@ import BigTeamHeader from './big-team-header' import BigTeamChannel from './big-team-channel' import {SmallTeam} from './small-team' import {BigTeamsLabel} from './big-teams-label' -import type * as T from '@/constants/types' +import type {ChatInboxRowItem} from '../rowitem' -const makeRow = (item: T.Chat.ChatInboxRowItem, navKey: string, selected: boolean) => { +const makeRow = (item: ChatInboxRowItem, navKey: string, selected: boolean) => { if (item.type === 'bigTeamsLabel') { return } diff --git a/shared/chat/inbox/row/opened-row-state.tsx b/shared/chat/inbox/row/opened-row-state.tsx index f32a8b91ce9a..29c1d4c415ff 100644 --- a/shared/chat/inbox/row/opened-row-state.tsx +++ b/shared/chat/inbox/row/opened-row-state.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Z from '@/util/zustand' import type * as T from '@/constants/types' diff --git a/shared/chat/inbox/row/sizes.tsx b/shared/chat/inbox/row/sizes.tsx index 584b245ccfed..027ee2a5c384 100644 --- a/shared/chat/inbox/row/sizes.tsx +++ b/shared/chat/inbox/row/sizes.tsx @@ -1,7 +1,7 @@ // In order for the inbox rows to be calculated quickly we use fixed sizes for each type so // in order for the list and the rows to ensure they're the same size we keep the sizes here import * as Kb from '@/common-adapters' -import type * as T from '@/constants/types' +import type {ChatInboxRowType} from '../rowitem' export const smallRowHeight = Kb.Styles.isMobile ? 64 : 56 export const bigRowHeight = Kb.Styles.isMobile ? 40 : 24 @@ -17,8 +17,8 @@ export const dividerHeight = (showingButton: boolean) => { } } -export const getRowHeight = (type: T.Chat.ChatInboxRowType, showingDividerButton: boolean) => { - const exhaustive = (type: T.Chat.ChatInboxRowType, showingDividerButton: boolean) => { +export const getRowHeight = (type: ChatInboxRowType, showingDividerButton: boolean) => { + const exhaustive = (type: ChatInboxRowType, showingDividerButton: boolean) => { switch (type) { case 'bigTeamsLabel': return bigHeaderHeight diff --git a/shared/chat/inbox/row/small-team/bottom-line.tsx b/shared/chat/inbox/row/small-team/bottom-line.tsx index eccb3d94d2bb..1dfbc213d35e 100644 --- a/shared/chat/inbox/row/small-team/bottom-line.tsx +++ b/shared/chat/inbox/row/small-team/bottom-line.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {SnippetContext, SnippetDecorationContext} from './contexts' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { layoutSnippet?: string diff --git a/shared/chat/inbox/row/small-team/index.tsx b/shared/chat/inbox/row/small-team/index.tsx index 869aaa3a9957..f0289b126b34 100644 --- a/shared/chat/inbox/row/small-team/index.tsx +++ b/shared/chat/inbox/row/small-team/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {SimpleTopLine} from './top-line' @@ -9,7 +9,7 @@ import * as RowSizes from '../sizes' import * as T from '@/constants/types' import SwipeConvActions from './swipe-conv-actions' import './small-team.css' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' import { IsTeamContext, ParticipantsContext, diff --git a/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx b/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx index 5ec1de0c2ee2..94ff76ff16f9 100644 --- a/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx +++ b/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as Reanimated from 'react-native-reanimated' diff --git a/shared/chat/inbox/row/small-team/top-line.tsx b/shared/chat/inbox/row/small-team/top-line.tsx index 86825d823584..3e45c439e679 100644 --- a/shared/chat/inbox/row/small-team/top-line.tsx +++ b/shared/chat/inbox/row/small-team/top-line.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import TeamMenu from '@/chat/conversation/info-panel/menu' diff --git a/shared/chat/inbox/row/teams-divider.tsx b/shared/chat/inbox/row/teams-divider.tsx index 282156d7da76..5c90d6eab859 100644 --- a/shared/chat/inbox/row/teams-divider.tsx +++ b/shared/chat/inbox/row/teams-divider.tsx @@ -1,14 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' import * as RowSizes from './sizes' +import type {ChatInboxRowItem} from '../rowitem' type Props = { hiddenCountDelta?: number smallTeamsExpanded: boolean - rows: Array + rows: Array showButton: boolean toggle: () => void style?: Kb.Styles.StylesCrossPlatform diff --git a/shared/constants/types/chat2/rowitem.tsx b/shared/chat/inbox/rowitem.tsx similarity index 100% rename from shared/constants/types/chat2/rowitem.tsx rename to shared/chat/inbox/rowitem.tsx diff --git a/shared/chat/inbox/search-row.tsx b/shared/chat/inbox/search-row.tsx index c97a2cc786bd..ceedb67615a9 100644 --- a/shared/chat/inbox/search-row.tsx +++ b/shared/chat/inbox/search-row.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import ChatFilterRow from './filter-row' import StartNewChat from './row/start-new-chat' diff --git a/shared/chat/new-team-dialog-container.tsx b/shared/chat/new-team-dialog-container.tsx index 0421c30d6fef..4c4d1864c28e 100644 --- a/shared/chat/new-team-dialog-container.tsx +++ b/shared/chat/new-team-dialog-container.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {CreateNewTeam} from '../teams/new-team' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import upperFirst from 'lodash/upperFirst' const NewTeamDialog = () => { diff --git a/shared/chat/payments/status/index.tsx b/shared/chat/payments/status/index.tsx index 3fe3ec65668b..05236a073498 100644 --- a/shared/chat/payments/status/index.tsx +++ b/shared/chat/payments/status/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Styles from '@/styles' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import PaymentStatusError from './error' import Text from '@/common-adapters/text' import {Box2} from '@/common-adapters/box' @@ -9,7 +9,7 @@ import type * as T from '@/constants/types' import type {MeasureRef} from '@/common-adapters/measure-ref' import type * as WalletTypes from '@/constants/types/wallets' import {useOrdinal} from '@/chat/conversation/messages/ids-context' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' // This is actually a dependency of common-adapters/markdown so we have to treat it like a common-adapter, no * import allowed const Kb = { diff --git a/shared/chat/pdf/index.desktop.tsx b/shared/chat/pdf/index.desktop.tsx index d0c53c8853c4..8866e8ec8316 100644 --- a/shared/chat/pdf/index.desktop.tsx +++ b/shared/chat/pdf/index.desktop.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type {Props} from '.' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const ChatPDF = (props: Props) => { const {ordinal} = props @@ -11,7 +11,7 @@ const ChatPDF = (props: Props) => { const title = message?.title || message?.fileName || 'PDF' const url = message?.fileURL const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const attachmentDownload = Chat.useChatContext(s => s.dispatch.attachmentDownload) diff --git a/shared/chat/pdf/index.native.tsx b/shared/chat/pdf/index.native.tsx index 044e8691e00b..d0900794f567 100644 --- a/shared/chat/pdf/index.native.tsx +++ b/shared/chat/pdf/index.native.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type {Props} from '.' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const ChatPDF = (props: Props) => { const {ordinal, url} = props @@ -12,7 +12,7 @@ const ChatPDF = (props: Props) => { const [error, setError] = React.useState('') const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const onBack = () => navigateUp() - const showShareActionSheet = useConfigState(s => s.dispatch.dynamic.showShareActionSheet) + const showShareActionSheet = useConfigState(s => s.dispatch.defer.showShareActionSheet) const onShare = () => { showShareActionSheet?.(url ?? '', '', 'application/pdf') } diff --git a/shared/chat/routes.tsx b/shared/chat/routes.tsx index b34bfe654bdd..d9dcd49db022 100644 --- a/shared/chat/routes.tsx +++ b/shared/chat/routes.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import chatNewChat from '../team-building/page' import {headerNavigationOptions} from './conversation/header-area' import inboxGetOptions from './inbox/get-options' diff --git a/shared/chat/selectable-big-team-channel-container.tsx b/shared/chat/selectable-big-team-channel-container.tsx index 19af38e9941a..35e0b58f9614 100644 --- a/shared/chat/selectable-big-team-channel-container.tsx +++ b/shared/chat/selectable-big-team-channel-container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import SelectableBigTeamChannel from './selectable-big-team-channel' type OwnProps = { diff --git a/shared/chat/selectable-small-team-container.tsx b/shared/chat/selectable-small-team-container.tsx index 2248c71d3a05..0a1f5f44b24a 100644 --- a/shared/chat/selectable-small-team-container.tsx +++ b/shared/chat/selectable-small-team-container.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type {AllowedColors} from '@/common-adapters/text' import SelectableSmallTeam from './selectable-small-team' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { filter?: string diff --git a/shared/chat/send-to-chat/index.tsx b/shared/chat/send-to-chat/index.tsx index 521f840c96bf..532f8a5a55fd 100644 --- a/shared/chat/send-to-chat/index.tsx +++ b/shared/chat/send-to-chat/index.tsx @@ -1,14 +1,14 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' import * as Kbfs from '@/fs/common' import ConversationList from './conversation-list/conversation-list' import ChooseConversation from './conversation-list/choose-conversation' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type Props = { canBack?: boolean diff --git a/shared/common-adapters/avatar/hooks.tsx b/shared/common-adapters/avatar/hooks.tsx index 0307b138030d..e1177a28b72f 100644 --- a/shared/common-adapters/avatar/hooks.tsx +++ b/shared/common-adapters/avatar/hooks.tsx @@ -1,5 +1,5 @@ // High level avatar class. Handdles converting from usernames to urls. Deals with testing mode. -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as React from 'react' import {iconTypeToImgSet, urlsToImgSet, urlsToSrcSet, urlsToBaseSrc, type IconType} from '../icon' import * as Styles from '@/styles' @@ -7,9 +7,9 @@ import * as AvatarZus from './store' import './avatar.css' import type {Props} from '.' import {useColorScheme} from 'react-native' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {navToProfile} from '@/constants/router2' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {navToProfile} from '@/stores/router2' export const avatarSizes = [128, 96, 64, 48, 32, 24, 16] as const export type AvatarSize = (typeof avatarSizes)[number] diff --git a/shared/common-adapters/avatar/store.tsx b/shared/common-adapters/avatar/store.tsx index da6964f9fc9a..c73d64efeff5 100644 --- a/shared/common-adapters/avatar/store.tsx +++ b/shared/common-adapters/avatar/store.tsx @@ -1,6 +1,7 @@ import type * as T from '@/constants/types' import * as Z from '@/util/zustand' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ counts: Map }> diff --git a/shared/common-adapters/copy-text.tsx b/shared/common-adapters/copy-text.tsx index 7942082307fb..43a797333631 100644 --- a/shared/common-adapters/copy-text.tsx +++ b/shared/common-adapters/copy-text.tsx @@ -8,7 +8,7 @@ import {useTimeout} from './use-timers' import * as Styles from '@/styles' import logger from '@/logger' import type {MeasureRef} from './measure-ref' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const Kb = { Box2Measure, @@ -61,8 +61,8 @@ const CopyText = (props: Props) => { const popupAnchor = React.useRef(null) const textRef = React.useRef(null) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) - const showShareActionSheet = useConfigState(s => s.dispatch.dynamic.showShareActionSheet) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) + const showShareActionSheet = useConfigState(s => s.dispatch.defer.showShareActionSheet) const copy = React.useCallback(() => { if (!text) { if (!loadText) { diff --git a/shared/common-adapters/markdown/channel.tsx b/shared/common-adapters/markdown/channel.tsx index 55a2dc3d237e..a80546942696 100644 --- a/shared/common-adapters/markdown/channel.tsx +++ b/shared/common-adapters/markdown/channel.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import Text, {type StylesTextCrossPlatform} from '../text' import * as React from 'react' diff --git a/shared/common-adapters/markdown/maybe-mention/index.tsx b/shared/common-adapters/markdown/maybe-mention/index.tsx index 33431631f674..86bbe6a0a396 100644 --- a/shared/common-adapters/markdown/maybe-mention/index.tsx +++ b/shared/common-adapters/markdown/maybe-mention/index.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import Text, {type StylesTextCrossPlatform} from '@/common-adapters/text' import Mention from '../../mention-container' diff --git a/shared/common-adapters/markdown/maybe-mention/team.tsx b/shared/common-adapters/markdown/maybe-mention/team.tsx index 90bd396a1ee5..e9b7f234f06b 100644 --- a/shared/common-adapters/markdown/maybe-mention/team.tsx +++ b/shared/common-adapters/markdown/maybe-mention/team.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import Text, {type StylesTextCrossPlatform} from '@/common-adapters/text' import {Box2} from '@/common-adapters/box' diff --git a/shared/common-adapters/markdown/service-decoration.tsx b/shared/common-adapters/markdown/service-decoration.tsx index 9951adafed94..90879a58e0b2 100644 --- a/shared/common-adapters/markdown/service-decoration.tsx +++ b/shared/common-adapters/markdown/service-decoration.tsx @@ -1,7 +1,7 @@ import * as T from '@/constants/types' import * as React from 'react' import * as C from '@/constants' -import {useDeepLinksState} from '@/constants/deeplinks' +import {handleAppLink} from '@/constants/deeplinks' import * as Styles from '@/styles' import Channel from './channel' import KbfsPath from '@/fs/common/kbfs-path' @@ -29,10 +29,9 @@ type KeybaseLinkProps = { } const KeybaseLink = (props: KeybaseLinkProps) => { - const handleAppLink = useDeepLinksState(s => s.dispatch.handleAppLink) const onClick = React.useCallback(() => { handleAppLink(props.link) - }, [handleAppLink, props.link]) + }, [props.link]) return ( { let {username} = ownProps diff --git a/shared/common-adapters/mention.tsx b/shared/common-adapters/mention.tsx index 468c66b4297e..973ec7212085 100644 --- a/shared/common-adapters/mention.tsx +++ b/shared/common-adapters/mention.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Styles from '@/styles' import {WithProfileCardPopup} from './profile-card' import Text from './text' diff --git a/shared/common-adapters/name-with-icon.tsx b/shared/common-adapters/name-with-icon.tsx index fdadf76d3ba6..07c7f2c4f3c7 100644 --- a/shared/common-adapters/name-with-icon.tsx +++ b/shared/common-adapters/name-with-icon.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Styles from '@/styles' import * as C from '@/constants' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import Avatar, {type AvatarSize} from './avatar' import {Box} from './box' import ClickableBox from './clickable-box' @@ -13,8 +13,8 @@ import Text, { type TextTypeBold, } from './text' import ConnectedUsernames from './usernames' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' type Size = 'smaller' | 'small' | 'default' | 'big' | 'huge' diff --git a/shared/common-adapters/profile-card.tsx b/shared/common-adapters/profile-card.tsx index 933bfa706f49..a053acccd437 100644 --- a/shared/common-adapters/profile-card.tsx +++ b/shared/common-adapters/profile-card.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Styles from '@/styles' import * as Platforms from '@/util/platforms' -import * as Tracker from '@/constants/tracker2' +import * as Tracker from '@/stores/tracker2' import type * as T from '@/constants/types' import capitalize from 'lodash/capitalize' import Box, {Box2, Box2Measure} from './box' @@ -12,16 +12,16 @@ import {_setWithProfileCardPopup} from './usernames' import FloatingMenu from './floating-menu' import Icon from './icon' import Meta from './meta' -import {useProfileState} from '@/constants/profile' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useProfileState} from '@/stores/profile' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' import ProgressIndicator from './progress-indicator' import Text from './text' import WithTooltip from './with-tooltip' import DelayedMounting from './delayed-mounting' import {type default as FollowButtonType} from '../profile/user/actions/follow-button' import type ChatButtonType from '../chat/chat-button' -import {useTrackerState} from '@/constants/tracker2' +import {useTrackerState} from '@/stores/tracker2' import type {MeasureRef} from './measure-ref' const positionFallbacks = ['top center', 'bottom center'] as const diff --git a/shared/common-adapters/proof-broken-banner.tsx b/shared/common-adapters/proof-broken-banner.tsx index 861ead336641..e1ecfd0cb296 100644 --- a/shared/common-adapters/proof-broken-banner.tsx +++ b/shared/common-adapters/proof-broken-banner.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' import * as React from 'react' import {Banner, BannerParagraph} from './banner' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' const Kb = {Banner} type Props = {users?: Array} diff --git a/shared/common-adapters/reload.tsx b/shared/common-adapters/reload.tsx index d9b92a06514e..e93d4d91c339 100644 --- a/shared/common-adapters/reload.tsx +++ b/shared/common-adapters/reload.tsx @@ -9,8 +9,8 @@ import Text from './text' import Button from './button' import Icon from './icon' import type {RPCError} from '@/util/errors' -import {settingsFeedbackTab} from '@/constants/settings/util' -import {useConfigState} from '@/constants/config' +import {settingsFeedbackTab} from '@/constants/settings' +import {useConfigState} from '@/stores/config' const Kb = { Box2, diff --git a/shared/common-adapters/team-with-popup.tsx b/shared/common-adapters/team-with-popup.tsx index 4059c9608948..f907e5e1ca80 100644 --- a/shared/common-adapters/team-with-popup.tsx +++ b/shared/common-adapters/team-with-popup.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import {Box2} from './box' import * as Styles from '@/styles' diff --git a/shared/common-adapters/usernames.tsx b/shared/common-adapters/usernames.tsx index e86fcb564666..570f91b6abab 100644 --- a/shared/common-adapters/usernames.tsx +++ b/shared/common-adapters/usernames.tsx @@ -12,11 +12,11 @@ import Text, { import {backgroundModeIsNegative} from './text.shared' import isArray from 'lodash/isArray' import type {e164ToDisplay as e164ToDisplayType} from '@/util/phone-numbers' -import {useTrackerState} from '@/constants/tracker2' -import {useUsersState} from '@/constants/users' -import {useProfileState} from '@/constants/profile' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useProfileState} from '@/stores/profile' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' export type User = { username: string diff --git a/shared/common-adapters/wave-button.tsx b/shared/common-adapters/wave-button.tsx index 40a33f359a66..63a8ab05699a 100644 --- a/shared/common-adapters/wave-button.tsx +++ b/shared/common-adapters/wave-button.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {Box2, Box} from './box' import Icon from './icon' diff --git a/shared/constants/active/index.tsx b/shared/constants/active/index.tsx deleted file mode 100644 index cf53cc33b318..000000000000 --- a/shared/constants/active/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as Chat from '../chat2' -import type * as T from '../types' -import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' - -type Store = T.Immutable<{active: boolean}> -const initialStore: Store = {active: true} - -export interface State extends Store { - dispatch: { - resetState: 'default' - setActive: (a: boolean) => void - } -} -export const useActiveState = Z.createZustand(set => { - const dispatch: State['dispatch'] = { - resetState: 'default', - setActive: a => { - set(s => { - s.active = a - }) - const cs = storeRegistry.getConvoState(Chat.getSelectedConversation()) - cs.dispatch.markThreadAsRead() - }, - } - return {...initialStore, dispatch} -}) diff --git a/shared/constants/archive/util.tsx b/shared/constants/archive/util.tsx deleted file mode 100644 index e44657ebf42e..000000000000 --- a/shared/constants/archive/util.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifySimpleFSSimpleFSArchiveStatusChanged: - case EngineGen.chat1NotifyChatChatArchiveComplete: - case EngineGen.chat1NotifyChatChatArchiveProgress: - { - storeRegistry.getState('archive').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/autoreset/util.tsx b/shared/constants/autoreset/util.tsx deleted file mode 100644 index ccaa494f61a0..000000000000 --- a/shared/constants/autoreset/util.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyBadgesBadgeState: - { - storeRegistry.getState('autoreset').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/bots/util.tsx b/shared/constants/bots/util.tsx deleted file mode 100644 index 1af60048ec8d..000000000000 --- a/shared/constants/bots/util.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import type * as T from '../types' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyFeaturedBotsFeaturedBotsUpdate: - { - storeRegistry.getState('bots').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} - - -export const getFeaturedSorted = ( - featuredBotsMap: ReadonlyMap -): Array => { - const featured = [...featuredBotsMap.values()] - featured.sort((a: T.RPCGen.FeaturedBot, b: T.RPCGen.FeaturedBot) => { - if (a.rank < b.rank) { - return 1 - } else if (a.rank > b.rank) { - return -1 - } - return 0 - }) - return featured -} diff --git a/shared/constants/chat2/common.tsx b/shared/constants/chat2/common.tsx index e69d40c23dc3..45ae0acc1c03 100644 --- a/shared/constants/chat2/common.tsx +++ b/shared/constants/chat2/common.tsx @@ -1,12 +1,12 @@ import * as T from '../types' import {isMobile, isTablet} from '../platform' -import * as Router2 from '../router2' -import {storeRegistry} from '../store-registry' +import {getVisibleScreen} from '@/constants/router2' +import {useConfigState} from '@/stores/config' export const explodingModeGregorKeyPrefix = 'exploding:' export const getSelectedConversation = (allowUnderModal: boolean = false): T.Chat.ConversationIDKey => { - const maybeVisibleScreen = Router2.getVisibleScreen(undefined, allowUnderModal) + const maybeVisibleScreen = getVisibleScreen(undefined, allowUnderModal) if (maybeVisibleScreen?.name === threadRouteName) { const mParams = maybeVisibleScreen.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} return mParams?.conversationIDKey ?? T.Chat.noConversationIDKey @@ -25,13 +25,12 @@ export const isUserActivelyLookingAtThisThread = (conversationIDKey: T.Chat.Conv if (!isSplit) { chatThreadSelected = true // conversationIDKey === selectedConversationIDKey is the only thing that matters in the new router } else { - const maybeVisibleScreen = Router2.getVisibleScreen() + const maybeVisibleScreen = getVisibleScreen() chatThreadSelected = (maybeVisibleScreen === undefined ? undefined : maybeVisibleScreen.name) === threadRouteName } - const {appFocused} = storeRegistry.getState('config') - const {active: userActive} = storeRegistry.getState('active') + const {appFocused, active: userActive} = useConfigState.getState() return ( appFocused && // app focused? diff --git a/shared/constants/chat2/debug.tsx b/shared/constants/chat2/debug.tsx index ba94fb89a7f8..e91c336a2aca 100644 --- a/shared/constants/chat2/debug.tsx +++ b/shared/constants/chat2/debug.tsx @@ -1,5 +1,5 @@ // Debug utilities for chat -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import logger from '@/logger' diff --git a/shared/constants/chat2/index.tsx b/shared/constants/chat2/index.tsx index 818915ca1b15..e69de29bb2d1 100644 --- a/shared/constants/chat2/index.tsx +++ b/shared/constants/chat2/index.tsx @@ -1,2001 +0,0 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise, type ViewPropsToPageProps} from '../utils' -import * as Tabs from '../tabs' -import * as EngineGen from '@/actions/engine-gen-gen' -import type * as ConfigConstants from '../config' -import * as Message from './message' -import * as Router2 from '../router2' -import * as TeamConstants from '../teams/util' -import logger from '@/logger' -import {RPCError} from '@/util/errors' -import * as Meta from './meta' -import {isMobile, isPhone} from '../platform' -import * as Z from '@/util/zustand' -import * as Common from './common' -import {clearChatStores, chatStores} from './convostate' -import {uint8ArrayToString} from 'uint8array-extras' -import isEqual from 'lodash/isEqual' -import {bodyToJSON} from '../rpc-utils' -import {navigateAppend, navUpToScreen, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' -import * as S from '../strings' - -const defaultTopReacjis = [ - {name: ':+1:'}, - {name: ':-1:'}, - {name: ':tada:'}, - {name: ':joy:'}, - {name: ':sunglasses:'}, -] -const defaultSkinTone = 1 -const defaultUserReacjis = {skinTone: defaultSkinTone, topReacjis: defaultTopReacjis} - -// while we're debugging chat issues -export const DEBUG_CHAT_DUMP = true - -const blockButtonsGregorPrefix = 'blockButtons.' - -export const inboxSearchMaxTextMessages = 25 -export const inboxSearchMaxTextResults = 50 -export const inboxSearchMaxNameResults = 7 -export const inboxSearchMaxUnreadNameResults = isMobile ? 5 : 10 - -export const makeInboxSearchInfo = (): T.Chat.InboxSearchInfo => ({ - botsResults: [], - botsResultsSuggested: false, - botsStatus: 'initial', - indexPercent: 0, - nameResults: [], - nameResultsUnread: false, - nameStatus: 'initial', - openTeamsResults: [], - openTeamsResultsSuggested: false, - openTeamsStatus: 'initial', - query: '', - selectedIndex: 0, - textResults: [], - textStatus: 'initial', -}) - -const getInboxSearchSelected = ( - inboxSearch: T.Immutable -): - | undefined - | { - conversationIDKey: T.Chat.ConversationIDKey - query?: string - } => { - const {selectedIndex, nameResults, botsResults, openTeamsResults, textResults} = inboxSearch - const firstTextResultIdx = botsResults.length + openTeamsResults.length + nameResults.length - const firstOpenTeamResultIdx = nameResults.length - - if (selectedIndex < firstOpenTeamResultIdx) { - const maybeNameResults = nameResults[selectedIndex] - const conversationIDKey = maybeNameResults === undefined ? undefined : maybeNameResults.conversationIDKey - if (conversationIDKey) { - return { - conversationIDKey, - query: undefined, - } - } - } else if (selectedIndex < firstTextResultIdx) { - return - } else if (selectedIndex >= firstTextResultIdx) { - const result = textResults[selectedIndex - firstTextResultIdx] - if (result) { - return { - conversationIDKey: result.conversationIDKey, - query: result.query, - } - } - } - return -} - -export const getMessageKey = (message: T.Chat.Message) => - `${message.conversationIDKey}:${T.Chat.ordinalToNumber(message.ordinal)}` - -export const getBotsAndParticipants = ( - meta: T.Immutable, - participantInfo: T.Immutable, - sort?: boolean -) => { - const isAdhocTeam = meta.teamType === 'adhoc' - const teamMembers = - storeRegistry.getState('teams').teamIDToMembers.get(meta.teamID) ?? new Map() - let bots: Array = [] - if (isAdhocTeam) { - bots = participantInfo.all.filter(p => !participantInfo.name.includes(p)) - } else { - bots = [...teamMembers.values()] - .filter( - p => - TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'restrictedbot') || - TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'bot') - ) - .map(p => p.username) - .sort((l, r) => l.localeCompare(r)) - } - let participants: ReadonlyArray = participantInfo.all - if (meta.channelname === 'general') { - participants = [...teamMembers.values()].reduce>((l, mi) => { - l.push(mi.username) - return l - }, []) - } - participants = participants.filter(p => !bots.includes(p)) - participants = sort - ? participants - .map(p => ({ - isAdmin: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'admin') : false, - isOwner: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'owner') : false, - username: p, - })) - .sort((l, r) => { - const leftIsAdmin = l.isAdmin || l.isOwner - const rightIsAdmin = r.isAdmin || r.isOwner - if (leftIsAdmin && !rightIsAdmin) { - return -1 - } else if (!leftIsAdmin && rightIsAdmin) { - return 1 - } - return l.username.localeCompare(r.username) - }) - .map(p => p.username) - : participants - return {bots, participants} -} - -export const getTeamMentionName = (name: string, channel: string) => { - return name + (channel ? `#${channel}` : '') -} - -export const isAssertion = (username: string) => username.includes('@') - -export const clampImageSize = (width: number, height: number, maxWidth: number, maxHeight: number) => { - const aspectRatio = width / height - - let newWidth = width - let newHeight = height - - if (newWidth > maxWidth) { - newWidth = maxWidth - newHeight = newWidth / aspectRatio - } - - if (newHeight > maxHeight) { - newHeight = maxHeight - newWidth = newHeight * aspectRatio - } - - return { - height: Math.ceil(newHeight), - width: Math.ceil(newWidth), - } -} - -export const zoomImage = (width: number, height: number, maxThumbSize: number) => { - const dims = - height > width - ? {height: (maxThumbSize * height) / width, width: maxThumbSize} - : {height: maxThumbSize, width: (maxThumbSize * width) / height} - const marginHeight = dims.height > maxThumbSize ? (dims.height - maxThumbSize) / 2 : 0 - const marginWidth = dims.width > maxThumbSize ? (dims.width - maxThumbSize) / 2 : 0 - return { - dims, - margins: { - marginBottom: -marginHeight, - marginLeft: -marginWidth, - marginRight: -marginWidth, - marginTop: -marginHeight, - }, - } -} - -const uiParticipantsToParticipantInfo = ( - uiParticipants: ReadonlyArray -): T.Chat.ParticipantInfo => { - const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} - uiParticipants.forEach(part => { - const {assertion, contactName, inConvName} = part - participantInfo.all.push(assertion) - if (inConvName) { - participantInfo.name.push(assertion) - } - if (contactName) { - participantInfo.contactName.set(assertion, contactName) - } - }) - return participantInfo -} - -/** - * Returns true if the team is big and you're a member - */ -export const isBigTeam = (state: State, teamID: string): boolean => { - const bigTeams = state.inboxLayout?.bigTeams - return (bigTeams || []).some(v => v.state === T.RPCChat.UIInboxBigTeamRowTyp.label && v.label.id === teamID) -} - -// prettier-ignore -type PreviewReason = - | 'appLink' | 'channelHeader' | 'convertAdHoc' | 'files' | 'forward' | 'fromAReset' - | 'journeyCardPopular' | 'manageView' | 'memberView' | 'messageLink' | 'newChannel' - | 'profile' | 'requestedPayment' | 'resetChatWithoutThem' | 'search' | 'sentPayment' - | 'teamHeader' | 'teamInvite' | 'teamMember' | 'teamMention' | 'teamRow' | 'tracker' | 'transaction' - -type Store = T.Immutable<{ - botPublicCommands: Map - createConversationError?: T.Chat.CreateConversationError - smallTeamBadgeCount: number - bigTeamBadgeCount: number - smallTeamsExpanded: boolean // if we're showing all small teams, - lastCoord?: T.Chat.Coordinate - paymentStatusMap: Map - staticConfig?: T.Chat.StaticConfig // static config stuff from the service. only needs to be loaded once. if null, it hasn't been loaded, - trustedInboxHasLoaded: boolean // if we've done initial trusted inbox load, - userReacjis: T.Chat.UserReacjis - userEmojis?: Array - userEmojisForAutocomplete?: Array - infoPanelShowing: boolean - infoPanelSelectedTab?: 'settings' | 'members' | 'attachments' | 'bots' - inboxNumSmallRows?: number - inboxHasLoaded: boolean // if we've ever loaded, - inboxLayout?: T.RPCChat.UIInboxLayout // layout of the inbox - inboxSearch?: T.Chat.InboxSearchInfo - teamIDToGeneralConvID: Map - flipStatusMap: Map - maybeMentionMap: Map - blockButtonsMap: Map // Should we show block buttons for this team ID? -}> - -const initialStore: Store = { - bigTeamBadgeCount: 0, - blockButtonsMap: new Map(), - botPublicCommands: new Map(), - createConversationError: undefined, - flipStatusMap: new Map(), - inboxHasLoaded: false, - inboxLayout: undefined, - inboxNumSmallRows: 5, - inboxSearch: undefined, - infoPanelSelectedTab: undefined, - infoPanelShowing: false, - lastCoord: undefined, - maybeMentionMap: new Map(), - paymentStatusMap: new Map(), - smallTeamBadgeCount: 0, - smallTeamsExpanded: false, - staticConfig: undefined, - teamIDToGeneralConvID: new Map(), - trustedInboxHasLoaded: false, - userEmojis: undefined, - userEmojisForAutocomplete: undefined, - userReacjis: defaultUserReacjis, -} - -export interface State extends Store { - dispatch: { - badgesUpdated: (badgeState?: T.RPCGen.BadgeState) => void - clearMetas: () => void - conversationErrored: ( - allowedUsers: ReadonlyArray, - disallowedUsers: ReadonlyArray, - code: number, - message: string - ) => void - createConversation: (participants: ReadonlyArray, highlightMessageID?: T.Chat.MessageID) => void - ensureWidgetMetas: () => void - findGeneralConvIDFromTeamID: (teamID: T.Teams.TeamID) => void - fetchUserEmoji: (conversationIDKey?: T.Chat.ConversationIDKey, onlyInTeam?: boolean) => void - inboxRefresh: ( - reason: - | 'bootstrap' - | 'componentNeverLoaded' - | 'inboxStale' - | 'inboxSyncedClear' - | 'inboxSyncedUnknown' - | 'joinedAConversation' - | 'leftAConversation' - | 'teamTypeChanged' - | 'maybeKickedFromTeam' - | 'widgetRefresh' - | 'shareConfigSearch' - ) => void - inboxSearch: (query: string) => void - inboxSearchMoveSelectedIndex: (increment: boolean) => void - inboxSearchSelect: ( - conversationIDKey?: T.Chat.ConversationIDKey, - query?: string, - selectedIndex?: number - ) => void - loadStaticConfig: () => void - loadedUserEmoji: (results: T.RPCChat.UserEmojiRes) => void - maybeChangeSelectedConv: () => void - messageSendByUsername: (username: string, text: string, waitingKey?: string) => void - metasReceived: ( - metas: ReadonlyArray, - removals?: ReadonlyArray // convs to remove - ) => void - navigateToInbox: (allowSwitchTab?: boolean) => void - onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => void - onEngineIncomingImpl: (action: EngineGen.Actions) => void - onChatInboxSynced: (action: EngineGen.Chat1NotifyChatChatInboxSyncedPayload) => void - onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => void - onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => void - onIncomingInboxUIItem: (inboxUIItem?: T.RPCChat.InboxUIItem) => void - onRouteChanged: (prev: T.Immutable, next: T.Immutable) => void - onTeamBuildingFinished: (users: ReadonlySet) => void - paymentInfoReceived: (paymentInfo: T.Chat.ChatPaymentInfo) => void - previewConversation: (p: { - participants?: ReadonlyArray - teamname?: string - channelname?: string - conversationIDKey?: T.Chat.ConversationIDKey // we only use this when we click on channel mentions. we could maybe change that plumbing but keeping it for now - highlightMessageID?: T.Chat.MessageID - reason: PreviewReason - }) => void - queueMetaToRequest: (ids: ReadonlyArray) => void - queueMetaHandle: () => void - refreshBotPublicCommands: (username: string) => void - resetConversationErrored: () => void - resetState: () => void - setMaybeMentionInfo: (name: string, info: T.RPCChat.UIMaybeMentionInfo) => void - setTrustedInboxHasLoaded: () => void - setInfoPanelTab: (tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void - setInboxNumSmallRows: (rows: number, ignoreWrite?: boolean) => void - toggleInboxSearch: (enabled: boolean) => void - toggleSmallTeamsExpanded: () => void - unboxRows: (ids: Array, force?: boolean) => void - updateCoinFlipStatus: (statuses: ReadonlyArray) => void - updateInboxLayout: (layout: string) => void - updateLastCoord: (coord: T.Chat.Coordinate) => void - updateUserReacjis: (userReacjis: T.RPCGen.UserReacjis) => void - updatedGregor: (items: ConfigConstants.State['gregorPushState']) => void - updateInfoPanel: (show: boolean, tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void - } - getBackCount: (conversationIDKey: T.Chat.ConversationIDKey) => number - getBadgeHiddenCount: (ids: Set) => {badgeCount: number; hiddenCount: number} - getUnreadIndicies: (ids: Array) => Map -} - -// Only get the untrusted conversations out -const untrustedConversationIDKeys = (ids: ReadonlyArray) => - ids.filter(id => storeRegistry.getConvoState(id).meta.trustedState === 'untrusted') - -// generic chat store -export const useChatState = Z.createZustand((set, get) => { - // We keep a set of conversations to unbox - let metaQueue = new Set() - - const dispatch: State['dispatch'] = { - badgesUpdated: b => { - if (!b) return - // clear all first - for (const [, cs] of chatStores) { - cs.getState().dispatch.badgesUpdated(0) - } - b.conversations?.forEach(c => { - const id = T.Chat.conversationIDToKey(c.convID) - storeRegistry.getConvoState(id).dispatch.badgesUpdated(c.badgeCount) - storeRegistry.getConvoState(id).dispatch.unreadUpdated(c.unreadMessages) - }) - const {bigTeamBadgeCount, smallTeamBadgeCount} = b - set(s => { - s.smallTeamBadgeCount = smallTeamBadgeCount - s.bigTeamBadgeCount = bigTeamBadgeCount - }) - }, - clearMetas: () => { - for (const [, cs] of chatStores) { - cs.getState().dispatch.setMeta() - } - }, - conversationErrored: (allowedUsers, disallowedUsers, code, message) => { - set(s => { - s.createConversationError = T.castDraft({ - allowedUsers, - code, - disallowedUsers, - message, - }) - }) - }, - createConversation: (participants, highlightMessageID) => { - // TODO This will break if you try to make 2 new conversations at the same time because there is - // only one pending conversation state. - // The fix involves being able to make multiple pending conversations - const f = async () => { - const username = storeRegistry.getState('current-user').username - if (!username) { - logger.error('Making a convo while logged out?') - return - } - try { - const result = await T.RPCChat.localNewConversationLocalRpcPromise( - { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - membersType: T.RPCChat.ConversationMembersType.impteamnative, - tlfName: [...new Set([username, ...participants])].join(','), - tlfVisibility: T.RPCGen.TLFVisibility.private, - topicType: T.RPCChat.TopicType.chat, - }, - S.waitingKeyChatCreating - ) - const {conv, uiConv} = result - const conversationIDKey = T.Chat.conversationIDToKey(conv.info.id) - if (!conversationIDKey) { - logger.warn("Couldn't make a new conversation?") - } else { - const meta = Meta.inboxUIItemToConversationMeta(uiConv) - if (meta) { - get().dispatch.metasReceived([meta]) - } - - const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( - uiConv.participants ?? [] - ) - if (participantInfo.all.length > 0) { - storeRegistry - .getConvoState(T.Chat.stringToConversationIDKey(uiConv.convID)) - .dispatch.setParticipants(participantInfo) - } - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.navigateToThread('justCreated', highlightMessageID) - } - } catch (error) { - if (error instanceof RPCError) { - const f = error.fields as Array<{key?: string}> | undefined - const errUsernames = f?.filter(elem => elem.key === 'usernames') as - | undefined - | Array<{key: string; value: string}> - let disallowedUsers: Array = [] - if (errUsernames?.length) { - const {value} = errUsernames[0] ?? {value: ''} - disallowedUsers = value.split(',') - } - const allowedUsers = participants.filter(x => !disallowedUsers.includes(x)) - get().dispatch.conversationErrored(allowedUsers, disallowedUsers, error.code, error.desc) - storeRegistry - .getConvoState(T.Chat.pendingErrorConversationIDKey) - .dispatch.navigateToThread('justCreated', highlightMessageID) - } - } - } - ignorePromise(f()) - }, - ensureWidgetMetas: () => { - const {inboxLayout} = get() - if (!inboxLayout?.widgetList) { - return - } - const missing = inboxLayout.widgetList.reduce>((l, v) => { - if (!storeRegistry.getConvoState(v.convID).isMetaGood()) { - l.push(v.convID) - } - return l - }, []) - if (missing.length === 0) { - return - } - get().dispatch.unboxRows(missing, true) - }, - fetchUserEmoji: (conversationIDKey, onlyInTeam) => { - const f = async () => { - const results = await T.RPCChat.localUserEmojisRpcPromise( - { - convID: - conversationIDKey && conversationIDKey !== T.Chat.noConversationIDKey - ? T.Chat.keyToConversationID(conversationIDKey) - : null, - opts: { - getAliases: true, - getCreationInfo: false, - onlyInTeam: onlyInTeam ?? false, - }, - }, - S.waitingKeyChatLoadingEmoji - ) - get().dispatch.loadedUserEmoji(results) - } - ignorePromise(f()) - }, - findGeneralConvIDFromTeamID: teamID => { - const f = async () => { - try { - const conv = await T.RPCChat.localFindGeneralConvFromTeamIDRpcPromise({teamID}) - const meta = Meta.inboxUIItemToConversationMeta(conv) - if (!meta) { - logger.info(`findGeneralConvIDFromTeamID: failed to convert to meta`) - return - } - get().dispatch.metasReceived([meta]) - set(s => { - s.teamIDToGeneralConvID.set(teamID, T.Chat.stringToConversationIDKey(conv.convID)) - }) - } catch (error) { - if (error instanceof RPCError) { - logger.info(`findGeneralConvIDFromTeamID: failed to get general conv: ${error.message}`) - } - } - } - ignorePromise(f()) - }, - inboxRefresh: reason => { - const f = async () => { - const {username} = storeRegistry.getState('current-user') - const {loggedIn} = storeRegistry.getState('config') - if (!loggedIn || !username) { - return - } - const clearExistingMetas = reason === 'inboxSyncedClear' - const clearExistingMessages = reason === 'inboxSyncedClear' - - logger.info(`Inbox refresh due to ${reason}`) - const reselectMode = - get().inboxHasLoaded || isPhone - ? T.RPCChat.InboxLayoutReselectMode.default - : T.RPCChat.InboxLayoutReselectMode.force - await T.RPCChat.localRequestInboxLayoutRpcPromise({reselectMode}) - if (clearExistingMetas) { - get().dispatch.clearMetas() - } - if (clearExistingMessages) { - for (const [, cs] of chatStores) { - cs.getState().dispatch.messagesClear() - } - } - } - ignorePromise(f()) - }, - inboxSearch: query => { - set(s => { - const {inboxSearch} = s - if (inboxSearch) { - inboxSearch.query = query - } - }) - const f = async () => { - const teamType = (t: T.RPCChat.TeamType) => (t === T.RPCChat.TeamType.complex ? 'big' : 'small') - - const onConvHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchConvHits']['inParam']) => { - const results = (resp.hits.hits || []).reduce>((arr, h) => { - arr.push({ - conversationIDKey: T.Chat.stringToConversationIDKey(h.convID), - name: h.name, - teamType: teamType(h.teamType), - }) - return arr - }, []) - - set(s => { - const unread = resp.hits.unreadMatches - const {inboxSearch} = s - if (inboxSearch?.nameStatus === 'inprogress') { - inboxSearch.nameResults = results - inboxSearch.nameResultsUnread = unread - inboxSearch.nameStatus = 'success' - } - }) - - const missingMetas = results.reduce>((arr, r) => { - if (!storeRegistry.getConvoState(r.conversationIDKey).isMetaGood()) { - arr.push(r.conversationIDKey) - } - return arr - }, []) - if (missingMetas.length > 0) { - get().dispatch.unboxRows(missingMetas, true) - } - } - - const onOpenTeamHits = ( - resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchTeamHits']['inParam'] - ) => { - const results = (resp.hits.hits || []).reduce>((arr, h) => { - const {description, name, memberCount, inTeam} = h - arr.push({ - description: description ?? '', - inTeam, - memberCount, - name, - publicAdmins: [], - }) - return arr - }, []) - const suggested = resp.hits.suggestedMatches - set(s => { - const {inboxSearch} = s - if (inboxSearch?.openTeamsStatus === 'inprogress') { - inboxSearch.openTeamsResultsSuggested = suggested - inboxSearch.openTeamsResults = T.castDraft(results) - inboxSearch.openTeamsStatus = 'success' - } - }) - } - - const onBotsHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchBotHits']['inParam']) => { - const results = resp.hits.hits || [] - const suggested = resp.hits.suggestedMatches - set(s => { - const {inboxSearch} = s - if (inboxSearch?.botsStatus === 'inprogress') { - inboxSearch.botsResultsSuggested = suggested - inboxSearch.botsResults = T.castDraft(results) - inboxSearch.botsStatus = 'success' - } - }) - } - - const onTextHit = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchInboxHit']['inParam']) => { - const {convID, convName, hits, query, teamType: tt, time} = resp.searchHit - - const result = { - conversationIDKey: T.Chat.conversationIDToKey(convID), - name: convName, - numHits: hits?.length ?? 0, - query, - teamType: teamType(tt), - time, - } as const - set(s => { - const {inboxSearch} = s - if (inboxSearch?.textStatus === 'inprogress') { - const {conversationIDKey} = result - const textResults = inboxSearch.textResults.filter( - r => r.conversationIDKey !== conversationIDKey - ) - textResults.push(result) - inboxSearch.textResults = textResults.sort((l, r) => r.time - l.time) - } - }) - - if ( - storeRegistry.getConvoState(result.conversationIDKey).meta.conversationIDKey === - T.Chat.noConversationIDKey - ) { - get().dispatch.unboxRows([result.conversationIDKey], true) - } - } - const onStart = () => { - set(s => { - const {inboxSearch} = s - if (inboxSearch) { - inboxSearch.nameStatus = 'inprogress' - inboxSearch.selectedIndex = 0 - inboxSearch.textResults = [] - inboxSearch.textStatus = 'inprogress' - inboxSearch.openTeamsStatus = 'inprogress' - inboxSearch.botsStatus = 'inprogress' - } - }) - } - const onDone = () => { - set(s => { - const status = 'success' - const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() - s.inboxSearch = T.castDraft(inboxSearch) - inboxSearch.textStatus = status - }) - } - - const onIndexStatus = ( - resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchIndexStatus']['inParam'] - ) => { - const percent = resp.status.percentIndexed - set(s => { - const {inboxSearch} = s - if (inboxSearch?.textStatus === 'inprogress') { - inboxSearch.indexPercent = percent - } - }) - } - - try { - await T.RPCChat.localSearchInboxRpcListener({ - incomingCallMap: { - 'chat.1.chatUi.chatSearchBotHits': onBotsHits, - 'chat.1.chatUi.chatSearchConvHits': onConvHits, - 'chat.1.chatUi.chatSearchInboxDone': onDone, - 'chat.1.chatUi.chatSearchInboxHit': onTextHit, - 'chat.1.chatUi.chatSearchInboxStart': onStart, - 'chat.1.chatUi.chatSearchIndexStatus': onIndexStatus, - 'chat.1.chatUi.chatSearchTeamHits': onOpenTeamHits, - }, - params: { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - namesOnly: false, - opts: { - afterContext: 0, - beforeContext: 0, - isRegex: false, - matchMentions: false, - maxBots: 10, - maxConvsHit: inboxSearchMaxTextResults, - maxConvsSearched: 0, - maxHits: inboxSearchMaxTextMessages, - maxMessages: -1, - maxNameConvs: query.length > 0 ? inboxSearchMaxNameResults : inboxSearchMaxUnreadNameResults, - maxTeams: 10, - reindexMode: T.RPCChat.ReIndexingMode.postsearchSync, - sentAfter: 0, - sentBefore: 0, - sentBy: '', - sentTo: '', - skipBotCache: false, - }, - query, - }, - }) - } catch (error) { - if (error instanceof RPCError) { - if (!(error.code === T.RPCGen.StatusCode.sccanceled)) { - logger.error('search failed: ' + error.message) - set(s => { - const status = 'error' - const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() - s.inboxSearch = T.castDraft(inboxSearch) - inboxSearch.textStatus = status - }) - } - } - } - } - ignorePromise(f()) - }, - inboxSearchMoveSelectedIndex: increment => { - set(s => { - const {inboxSearch} = s - if (inboxSearch) { - const {selectedIndex} = inboxSearch - const totalResults = inboxSearch.nameResults.length + inboxSearch.textResults.length - if (increment && selectedIndex < totalResults - 1) { - inboxSearch.selectedIndex = selectedIndex + 1 - } else if (!increment && selectedIndex > 0) { - inboxSearch.selectedIndex = selectedIndex - 1 - } - } - }) - }, - inboxSearchSelect: (_conversationIDKey, q, selectedIndex) => { - let conversationIDKey = _conversationIDKey - let query = q - set(s => { - const {inboxSearch} = s - if (inboxSearch && selectedIndex !== undefined) { - inboxSearch.selectedIndex = selectedIndex - } - }) - - const {inboxSearch} = get() - if (!inboxSearch) { - return - } - const selected = getInboxSearchSelected(inboxSearch) - if (!conversationIDKey) { - conversationIDKey = selected?.conversationIDKey - } - - if (!conversationIDKey) { - return - } - if (!query) { - query = selected?.query - } - - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('inboxSearch') - if (query) { - const cs = storeRegistry.getConvoState(conversationIDKey) - cs.dispatch.setThreadSearchQuery(query) - cs.dispatch.toggleThreadSearch(false) - cs.dispatch.threadSearch(query) - } else { - get().dispatch.toggleInboxSearch(false) - } - }, - loadStaticConfig: () => { - if (get().staticConfig) { - return - } - const {handshakeVersion, dispatch} = storeRegistry.getState('daemon') - const f = async () => { - const name = 'chat.loadStatic' - dispatch.wait(name, handshakeVersion, true) - try { - const res = await T.RPCChat.localGetStaticConfigRpcPromise() - if (!res.deletableByDeleteHistory) { - logger.error('chat.loadStaticConfig: got no deletableByDeleteHistory in static config') - return - } - const deletableByDeleteHistory = res.deletableByDeleteHistory.reduce>( - (res, type) => { - const ourTypes = Message.serviceMessageTypeToMessageTypes(type) - res.push(...ourTypes) - return res - }, - [] - ) - set(s => { - s.staticConfig = { - builtinCommands: (res.builtinCommands || []).reduce( - (map, c) => { - map[c.typ] = T.castDraft(c.commands) || [] - return map - }, - { - [T.RPCChat.ConversationBuiltinCommandTyp.none]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.adhoc]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.smallteam]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.bigteam]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.bigteamgeneral]: [], - } - ), - deletableByDeleteHistory: new Set(deletableByDeleteHistory), - } - }) - } finally { - dispatch.wait(name, handshakeVersion, false) - } - } - ignorePromise(f()) - }, - loadedUserEmoji: results => { - set(s => { - const newEmojis: Array = [] - results.emojis.emojis?.forEach(group => { - group.emojis?.forEach(e => newEmojis.push(e)) - }) - s.userEmojisForAutocomplete = newEmojis - s.userEmojis = T.castDraft(results.emojis.emojis) ?? [] - }) - }, - maybeChangeSelectedConv: () => { - const {inboxLayout} = get() - const newConvID = inboxLayout?.reselectInfo?.newConvID - const oldConvID = inboxLayout?.reselectInfo?.oldConvID - - const selectedConversation = Common.getSelectedConversation() - - if (!newConvID && !oldConvID) { - return - } - - const existingValid = T.Chat.isValidConversationIDKey(selectedConversation) - // no new id, just take the opportunity to resolve - if (!newConvID) { - if (!existingValid && isPhone) { - logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) - get().dispatch.navigateToInbox(false) - } - return - } - // not matching? - if (selectedConversation !== oldConvID) { - if (!existingValid && isPhone) { - logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) - get().dispatch.navigateToInbox(false) - } - return - } - // matching - if (isPhone) { - // on mobile just head back to the inbox if we have something selected - if (T.Chat.isValidConversationIDKey(selectedConversation)) { - logger.info(`maybeChangeSelectedConv: mobile: navigating up on conv change`) - get().dispatch.navigateToInbox(false) - return - } - logger.info(`maybeChangeSelectedConv: mobile: ignoring conv change, no conv selected`) - return - } else { - logger.info( - `maybeChangeSelectedConv: selecting new conv: new:${newConvID} old:${oldConvID} prevselected ${selectedConversation}` - ) - storeRegistry.getConvoState(newConvID).dispatch.navigateToThread('findNewestConversation') - } - }, - messageSendByUsername: (username, text, waitingKey) => { - const f = async () => { - const tlfName = `${storeRegistry.getState('current-user').username},${username}` - try { - const result = await T.RPCChat.localNewConversationLocalRpcPromise( - { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - membersType: T.RPCChat.ConversationMembersType.impteamnative, - tlfName, - tlfVisibility: T.RPCGen.TLFVisibility.private, - topicType: T.RPCChat.TopicType.chat, - }, - waitingKey - ) - storeRegistry - .getConvoState(T.Chat.conversationIDToKey(result.conv.info.id)) - .dispatch.sendMessage(text) - } catch (error) { - if (error instanceof RPCError) { - logger.warn('Could not send in messageSendByUsernames', error.message) - } - } - } - ignorePromise(f()) - }, - metasReceived: (metas, removals) => { - removals?.forEach(r => { - storeRegistry.getConvoState(r).dispatch.setMeta() - }) - metas.forEach(m => { - const {meta: oldMeta, dispatch, isMetaGood} = storeRegistry.getConvoState(m.conversationIDKey) - if (isMetaGood()) { - dispatch.updateMeta(Meta.updateMeta(oldMeta, m)) - } else { - dispatch.setMeta(m) - } - }) - - const selectedConversation = Common.getSelectedConversation() - const {isMetaGood, meta} = storeRegistry.getConvoState(selectedConversation) - if (isMetaGood()) { - const {teamID} = meta - if (!storeRegistry.getState('teams').teamIDToMembers.get(teamID) && meta.teamname) { - storeRegistry.getState('teams').dispatch.getMembers(teamID) - } - } - }, - navigateToInbox: (allowSwitchTab = true) => { - // components can call us during render sometimes so always defer - setTimeout(() => { - navUpToScreen('chatRoot') - if (allowSwitchTab) { - switchTab(Tabs.chatTab) - } - }, 1) - }, - onChatInboxSynced: action => { - const {syncRes} = action.payload.params - const {clear} = storeRegistry.getState('waiting').dispatch - const {inboxRefresh} = get().dispatch - clear(S.waitingKeyChatInboxSyncStarted) - - switch (syncRes.syncType) { - // Just clear it all - case T.RPCChat.SyncInboxResType.clear: - inboxRefresh('inboxSyncedClear') - break - // We're up to date - case T.RPCChat.SyncInboxResType.current: - break - // We got some new messages appended - case T.RPCChat.SyncInboxResType.incremental: { - const items = syncRes.incremental.items || [] - const selectedConversation = Common.getSelectedConversation() - let loadMore = false as boolean - const metas = items.reduce>((arr, i) => { - const meta = Meta.unverifiedInboxUIItemToConversationMeta(i.conv) - if (meta) { - arr.push(meta) - if (meta.conversationIDKey === selectedConversation) { - loadMore = true - } - } - return arr - }, []) - if (loadMore) { - storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) - } - const removals = syncRes.incremental.removals?.map(T.Chat.stringToConversationIDKey) - // Update new untrusted - if (metas.length || removals?.length) { - get().dispatch.metasReceived(metas, removals) - } - - get().dispatch.unboxRows( - items.filter(i => i.shouldUnbox).map(i => T.Chat.stringToConversationIDKey(i.conv.convID)), - true - ) - break - } - default: - inboxRefresh('inboxSyncedUnknown') - } - }, - onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => { - const {updates} = action.payload.params - const keys = ['clear', 'newactivity'] as const - if (__DEV__) { - if (keys.length * 2 !== Object.keys(T.RPCChat.StaleUpdateType).length) { - throw new Error('onChatThreadStale invalid enum') - } - } - let loadMore = false as boolean - const selectedConversation = Common.getSelectedConversation() - keys.forEach(key => { - const conversationIDKeys = (updates || []).reduce>((arr, u) => { - const cid = T.Chat.conversationIDToKey(u.convID) - if (u.updateType === T.RPCChat.StaleUpdateType[key]) { - arr.push(cid) - } - // mentioned? - if (cid === selectedConversation) { - loadMore = true - } - return arr - }, []) - // load the inbox instead - if (conversationIDKeys.length > 0) { - logger.info( - `onChatThreadStale: dispatching thread reload actions for ${conversationIDKeys.length} convs of type ${key}` - ) - get().dispatch.unboxRows(conversationIDKeys, true) - if (T.RPCChat.StaleUpdateType[key] === T.RPCChat.StaleUpdateType.clear) { - conversationIDKeys.forEach(convID => storeRegistry.getConvoState(convID).dispatch.messagesClear()) - } - } - }) - if (loadMore) { - storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) - } - }, - onEngineIncomingImpl: action => { - switch (action.type) { - case EngineGen.chat1ChatUiChatInboxFailed: // fallthrough - case EngineGen.chat1NotifyChatChatSetConvSettings: // fallthrough - case EngineGen.chat1NotifyChatChatAttachmentUploadStart: // fallthrough - case EngineGen.chat1NotifyChatChatPromptUnfurl: // fallthrough - case EngineGen.chat1NotifyChatChatPaymentInfo: // fallthrough - case EngineGen.chat1NotifyChatChatRequestInfo: // fallthrough - case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: //fallthrough - case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: //fallthrough - case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: { - const {convID} = action.payload.params - const conversationIDKey = T.Chat.conversationIDToKey(convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) - break - } - case EngineGen.chat1ChatUiChatCommandMarkdown: //fallthrough - case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: // fallthrough - case EngineGen.chat1ChatUiChatCommandStatus: // fallthrough - case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: //fallthrough - case EngineGen.chat1ChatUiChatGiphySearchResults: { - const {convID} = action.payload.params - const conversationIDKey = T.Chat.stringToConversationIDKey(convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) - break - } - case EngineGen.chat1NotifyChatChatParticipantsInfo: { - const {participants: participantMap} = action.payload.params - Object.keys(participantMap ?? {}).forEach(convIDStr => { - const participants = participantMap?.[convIDStr] - const conversationIDKey = T.Chat.stringToConversationIDKey(convIDStr) - if (participants) { - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.setParticipants(uiParticipantsToParticipantInfo(participants)) - } - }) - break - } - case EngineGen.chat1ChatUiChatMaybeMentionUpdate: { - const {teamName, channel, info} = action.payload.params - get().dispatch.setMaybeMentionInfo(getTeamMentionName(teamName, channel), info) - break - } - case EngineGen.chat1NotifyChatChatConvUpdate: { - const {conv} = action.payload.params - if (conv) { - const meta = Meta.inboxUIItemToConversationMeta(conv) - meta && get().dispatch.metasReceived([meta]) - } - break - } - case EngineGen.chat1ChatUiChatCoinFlipStatus: { - const {statuses} = action.payload.params - get().dispatch.updateCoinFlipStatus(statuses || []) - break - } - case EngineGen.chat1NotifyChatChatThreadsStale: - get().dispatch.onChatThreadStale(action) - break - case EngineGen.chat1NotifyChatChatSubteamRename: { - const {convs} = action.payload.params - const conversationIDKeys = (convs ?? []).map(c => T.Chat.stringToConversationIDKey(c.convID)) - get().dispatch.unboxRows(conversationIDKeys, true) - break - } - case EngineGen.chat1NotifyChatChatTLFFinalize: - get().dispatch.unboxRows([T.Chat.conversationIDToKey(action.payload.params.convID)]) - break - case EngineGen.chat1NotifyChatChatIdentifyUpdate: { - // Some participants are broken/fixed now - const {update} = action.payload.params - const usernames = update.CanonicalName.split(',') - const broken = (update.breaks.breaks || []).map(b => b.user.username) - const updates = usernames.map(name => ({info: {broken: broken.includes(name)}, name})) - storeRegistry.getState('users').dispatch.updates(updates) - break - } - case EngineGen.chat1ChatUiChatInboxUnverified: - get().dispatch.onGetInboxUnverifiedConvs(action) - break - case EngineGen.chat1NotifyChatChatInboxSyncStarted: - storeRegistry.getState('waiting').dispatch.increment(S.waitingKeyChatInboxSyncStarted) - break - - case EngineGen.chat1NotifyChatChatInboxSynced: - get().dispatch.onChatInboxSynced(action) - break - case EngineGen.chat1ChatUiChatInboxLayout: - get().dispatch.updateInboxLayout(action.payload.params.layout) - get().dispatch.maybeChangeSelectedConv() - get().dispatch.ensureWidgetMetas() - break - case EngineGen.chat1NotifyChatChatInboxStale: - get().dispatch.inboxRefresh('inboxStale') - break - case EngineGen.chat1ChatUiChatInboxConversation: - get().dispatch.onGetInboxConvsUnboxed(action) - break - case EngineGen.chat1NotifyChatNewChatActivity: { - const {activity} = action.payload.params - switch (activity.activityType) { - case T.RPCChat.ChatActivityType.incomingMessage: { - const {incomingMessage} = activity - const conversationIDKey = T.Chat.conversationIDToKey(incomingMessage.convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onIncomingMessage(incomingMessage) - get().dispatch.onIncomingInboxUIItem(incomingMessage.conv ?? undefined) - break - } - case T.RPCChat.ChatActivityType.setStatus: - get().dispatch.onIncomingInboxUIItem(activity.setStatus.conv ?? undefined) - break - case T.RPCChat.ChatActivityType.readMessage: - get().dispatch.onIncomingInboxUIItem(activity.readMessage.conv ?? undefined) - break - case T.RPCChat.ChatActivityType.newConversation: - get().dispatch.onIncomingInboxUIItem(activity.newConversation.conv ?? undefined) - break - case T.RPCChat.ChatActivityType.failedMessage: { - const {failedMessage} = activity - get().dispatch.onIncomingInboxUIItem(failedMessage.conv ?? undefined) - const {outboxRecords} = failedMessage - if (!outboxRecords) return - for (const outboxRecord of outboxRecords) { - const s = outboxRecord.state - if (s.state !== T.RPCChat.OutboxStateType.error) return - const {error} = s - const conversationIDKey = T.Chat.conversationIDToKey(outboxRecord.convID) - const outboxID = T.Chat.rpcOutboxIDToOutboxID(outboxRecord.outboxID) - // This is temp until fixed by CORE-7112. We get this error but not the call to let us show the red banner - const reason = Message.rpcErrorToString(error) - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.onMessageErrored(outboxID, reason, error.typ) - - if (error.typ === T.RPCChat.OutboxErrorType.identify) { - // Find out the user who failed identify - const match = error.message.match(/"(.*)"/) - const tempForceRedBox = match?.[1] - if (tempForceRedBox) { - storeRegistry - .getState('users') - .dispatch.updates([{info: {broken: true}, name: tempForceRedBox}]) - } - } - } - break - } - case T.RPCChat.ChatActivityType.membersUpdate: - get().dispatch.unboxRows([T.Chat.conversationIDToKey(activity.membersUpdate.convID)], true) - break - case T.RPCChat.ChatActivityType.setAppNotificationSettings: { - const {setAppNotificationSettings} = activity - const conversationIDKey = T.Chat.conversationIDToKey(setAppNotificationSettings.convID) - const settings = setAppNotificationSettings.settings - const cs = storeRegistry.getConvoState(conversationIDKey) - if (cs.isMetaGood()) { - cs.dispatch.updateMeta(Meta.parseNotificationSettings(settings)) - } - break - } - case T.RPCChat.ChatActivityType.expunge: { - // Get actions to update messagemap / metamap when retention policy expunge happens - const {expunge} = activity - const conversationIDKey = T.Chat.conversationIDToKey(expunge.convID) - const staticConfig = get().staticConfig - // The types here are askew. It confuses frontend MessageType with protocol MessageType. - // Placeholder is an example where it doesn't make sense. - const deletableMessageTypes = staticConfig?.deletableByDeleteHistory || Common.allMessageTypes - storeRegistry.getConvoState(conversationIDKey).dispatch.messagesWereDeleted({ - deletableMessageTypes, - upToMessageID: T.Chat.numberToMessageID(expunge.expunge.upto), - }) - break - } - case T.RPCChat.ChatActivityType.ephemeralPurge: { - const {ephemeralPurge} = activity - // Get actions to update messagemap / metamap when ephemeral messages expire - const conversationIDKey = T.Chat.conversationIDToKey(ephemeralPurge.convID) - const messageIDs = ephemeralPurge.msgs?.reduce>((arr, msg) => { - const msgID = Message.getMessageID(msg) - if (msgID) { - arr.push(msgID) - } - return arr - }, []) - - !!messageIDs && - storeRegistry.getConvoState(conversationIDKey).dispatch.messagesExploded(messageIDs) - break - } - case T.RPCChat.ChatActivityType.reactionUpdate: { - // Get actions to update the messagemap when reactions are updated - const {reactionUpdate} = activity - const conversationIDKey = T.Chat.conversationIDToKey(reactionUpdate.convID) - if (!reactionUpdate.reactionUpdates || reactionUpdate.reactionUpdates.length === 0) { - logger.warn(`Got ReactionUpdateNotif with no reactionUpdates for convID=${conversationIDKey}`) - break - } - const updates = reactionUpdate.reactionUpdates.map(ru => ({ - reactions: Message.reactionMapToReactions(ru.reactions), - targetMsgID: T.Chat.numberToMessageID(ru.targetMsgID), - })) - logger.info(`Got ${updates.length} reaction updates for convID=${conversationIDKey}`) - storeRegistry.getConvoState(conversationIDKey).dispatch.updateReactions(updates) - get().dispatch.updateUserReacjis(reactionUpdate.userReacjis) - break - } - case T.RPCChat.ChatActivityType.messagesUpdated: { - const {messagesUpdated} = activity - const conversationIDKey = T.Chat.conversationIDToKey(messagesUpdated.convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onMessagesUpdated(messagesUpdated) - break - } - default: - } - break - } - case EngineGen.chat1NotifyChatChatTypingUpdate: { - const {typingUpdates} = action.payload.params - typingUpdates?.forEach(u => { - storeRegistry - .getConvoState(T.Chat.conversationIDToKey(u.convID)) - .dispatch.setTyping(new Set(u.typers?.map(t => t.username))) - }) - break - } - case EngineGen.chat1NotifyChatChatSetConvRetention: { - const {conv, convID} = action.payload.params - if (!conv) { - logger.warn('onChatSetConvRetention: no conv given') - return - } - const meta = Meta.inboxUIItemToConversationMeta(conv) - if (!meta) { - logger.warn(`onChatSetConvRetention: no meta found for ${convID.toString()}`) - return - } - const cs = storeRegistry.getConvoState(meta.conversationIDKey) - // only insert if the convo is already in the inbox - if (cs.isMetaGood()) { - cs.dispatch.setMeta(meta) - } - break - } - case EngineGen.chat1NotifyChatChatSetTeamRetention: { - const {convs} = action.payload.params - const metas = (convs ?? []).reduce>((l, c) => { - const meta = Meta.inboxUIItemToConversationMeta(c) - if (meta) { - l.push(meta) - } - return l - }, []) - if (metas.length) { - metas.forEach(meta => { - const cs = storeRegistry.getConvoState(meta.conversationIDKey) - // only insert if the convo is already in the inbox - if (cs.isMetaGood()) { - cs.dispatch.setMeta(meta) - } - }) - storeRegistry.getState('teams').dispatch.updateTeamRetentionPolicy(metas) - } - // this is a more serious problem, but we don't need to bug the user about it - logger.error( - 'got NotifyChat.ChatSetTeamRetention with no attached InboxUIItems. The local version may be out of date' - ) - break - } - case EngineGen.keybase1NotifyBadgesBadgeState: { - const {badgeState} = action.payload.params - get().dispatch.badgesUpdated(badgeState) - break - } - case EngineGen.keybase1GregorUIPushState: { - const {state} = action.payload.params - const items = state.items || [] - const goodState = items.reduce>( - (arr, {md, item}) => { - md && item && arr.push({item, md}) - return arr - }, - [] - ) - if (goodState.length !== items.length) { - logger.warn('Lost some messages in filtering out nonNull gregor items') - } - get().dispatch.updatedGregor(goodState) - break - } - default: - } - }, - onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => { - // TODO not reactive - const {infoMap} = storeRegistry.getState('users') - const {convs} = action.payload.params - const inboxUIItems = JSON.parse(convs) as Array - const metas: Array = [] - let added = false as boolean - const usernameToFullname: {[username: string]: string} = {} - inboxUIItems.forEach(inboxUIItem => { - const meta = Meta.inboxUIItemToConversationMeta(inboxUIItem) - if (meta) { - metas.push(meta) - } - const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( - inboxUIItem.participants ?? [] - ) - if (participantInfo.all.length > 0) { - storeRegistry - .getConvoState(T.Chat.stringToConversationIDKey(inboxUIItem.convID)) - .dispatch.setParticipants(participantInfo) - } - inboxUIItem.participants?.forEach((part: T.RPCChat.UIParticipant) => { - const {assertion, fullName} = part - if (!infoMap.get(assertion) && fullName) { - added = true - usernameToFullname[assertion] = fullName - } - }) - }) - if (added) { - storeRegistry.getState('users').dispatch.updates( - Object.keys(usernameToFullname).map(name => ({ - info: {fullname: usernameToFullname[name]}, - name, - })) - ) - } - if (metas.length > 0) { - get().dispatch.metasReceived(metas) - } - }, - onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => { - const {inbox} = action.payload.params - const result = JSON.parse(inbox) as T.RPCChat.UnverifiedInboxUIItems - const items: ReadonlyArray = result.items ?? [] - // We get a subset of meta information from the cache even in the untrusted payload - const metas = items.reduce>((arr, item) => { - const m = Meta.unverifiedInboxUIItemToConversationMeta(item) - m && arr.push(m) - return arr - }, []) - get().dispatch.setTrustedInboxHasLoaded() - // Check if some of our existing stored metas might no longer be valid - get().dispatch.metasReceived(metas) - }, - onIncomingInboxUIItem: conv => { - if (!conv) return - const meta = Meta.inboxUIItemToConversationMeta(conv) - const usernameToFullname = (conv.participants ?? []).reduce<{[key: string]: string}>((map, part) => { - if (part.fullName) { - map[part.assertion] = part.fullName - } - return map - }, {}) - - storeRegistry.getState('users').dispatch.updates( - Object.keys(usernameToFullname).map(name => ({ - info: {fullname: usernameToFullname[name]}, - name, - })) - ) - - if (meta) { - get().dispatch.metasReceived([meta]) - } - }, - onRouteChanged: (prev, next) => { - const maybeChangeChatSelection = () => { - const wasModal = prev && Router2.getModalStack(prev).length > 0 - const isModal = next && Router2.getModalStack(next).length > 0 - // ignore if changes involve a modal - if (wasModal || isModal) { - return - } - const p = Router2.getVisibleScreen(prev) - const n = Router2.getVisibleScreen(next) - const wasChat = p?.name === Common.threadRouteName - const isChat = n?.name === Common.threadRouteName - // nothing to do with chat - if (!wasChat && !isChat) { - return - } - const pParams = p?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} - const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} - const wasID = pParams?.conversationIDKey - const isID = nParams?.conversationIDKey - - logger.info('maybeChangeChatSelection ', {isChat, isID, wasChat, wasID}) - - // same? ignore - if (wasChat && isChat && wasID === isID) { - // if we've never loaded anything, keep going so we load it - if (!isID || storeRegistry.getConvoState(isID).loaded) { - return - } - } - - // deselect if there was one - const deselectAction = () => { - if (wasChat && wasID && T.Chat.isValidConversationIDKey(wasID)) { - get().dispatch.unboxRows([wasID], true) - // needed? - // storeRegistry.getConvoState(wasID).dispatch.clearOrangeLine('deselected') - } - } - - // still chatting? just select new one - if (wasChat && isChat && isID && T.Chat.isValidConversationIDKey(isID)) { - deselectAction() - storeRegistry.getConvoState(isID).dispatch.selectedConversation() - return - } - - // leaving a chat - if (wasChat && !isChat) { - deselectAction() - return - } - - // going into a chat - if (isChat && isID && T.Chat.isValidConversationIDKey(isID)) { - deselectAction() - storeRegistry.getConvoState(isID).dispatch.selectedConversation() - return - } - } - - const maybeChatTabSelected = () => { - if (Router2.getTab(prev) !== Tabs.chatTab && Router2.getTab(next) === Tabs.chatTab) { - const n = Router2.getVisibleScreen(next) - const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} - const isID = nParams?.conversationIDKey - isID && storeRegistry.getConvoState(isID).dispatch.tabSelected() - } - } - maybeChangeChatSelection() - maybeChatTabSelected() - }, - onTeamBuildingFinished: users => { - const f = async () => { - // need to let the mdoal hide first else its thrashy - await timeoutPromise(500) - storeRegistry - .getConvoState(T.Chat.pendingWaitingConversationIDKey) - .dispatch.navigateToThread('justCreated') - get().dispatch.createConversation([...users].map(u => u.id)) - } - ignorePromise(f()) - }, - paymentInfoReceived: paymentInfo => { - set(s => { - s.paymentStatusMap.set(paymentInfo.paymentID, paymentInfo) - }) - }, - previewConversation: p => { - // We always make adhoc convos and never preview it - const previewConversationPersonMakesAConversation = () => { - const {participants, teamname, highlightMessageID} = p - if (teamname) return - if (!participants) return - const toFind = [...participants].sort().join(',') - const toFindN = participants.length - for (const cs of chatStores.values()) { - const names = cs.getState().participants.name - if (names.length !== toFindN) continue - const p = [...names].sort().join(',') - if (p === toFind) { - storeRegistry - .getConvoState(cs.getState().id) - .dispatch.navigateToThread('justCreated', highlightMessageID) - return - } - } - - storeRegistry - .getConvoState(T.Chat.pendingWaitingConversationIDKey) - .dispatch.navigateToThread('justCreated') - get().dispatch.createConversation(participants, highlightMessageID) - } - - // We preview channels - const previewConversationTeam = async () => { - const {conversationIDKey, highlightMessageID, teamname, reason} = p - if (conversationIDKey) { - if ( - reason === 'messageLink' || - reason === 'teamMention' || - reason === 'channelHeader' || - reason === 'manageView' - ) { - // Add preview channel to inbox - await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ - convID: T.Chat.keyToConversationID(conversationIDKey), - }) - } - - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.navigateToThread('previewResolved', highlightMessageID) - return - } - - if (!teamname) { - return - } - - const channelname = p.channelname || 'general' - try { - const results = await T.RPCChat.localFindConversationsLocalRpcPromise({ - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - membersType: T.RPCChat.ConversationMembersType.team, - oneChatPerTLF: true, - tlfName: teamname, - topicName: channelname, - topicType: T.RPCChat.TopicType.chat, - visibility: T.RPCGen.TLFVisibility.private, - }) - const resultMetas = (results.uiConversations || []) - .map(row => Meta.inboxUIItemToConversationMeta(row)) - .filter(Boolean) - - const first = resultMetas[0] - if (!first) { - if (p.reason === 'appLink') { - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - "We couldn't find this team chat channel. Please check that you're a member of the team and the channel exists." - ) - navigateAppend('keybaseLinkError') - return - } else { - return - } - } - - const results2 = await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ - convID: T.Chat.keyToConversationID(first.conversationIDKey), - }) - const meta = Meta.inboxUIItemToConversationMeta(results2.conv) - if (meta) { - storeRegistry.getState('chat').dispatch.metasReceived([meta]) - } - - storeRegistry - .getConvoState(first.conversationIDKey) - .dispatch.navigateToThread('previewResolved', highlightMessageID) - } catch (error) { - if ( - error instanceof RPCError && - error.code === T.RPCGen.StatusCode.scteamnotfound && - reason === 'appLink' - ) { - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - "We couldn't find this team. Please check that you're a member of the team and the channel exists." - ) - navigateAppend('keybaseLinkError') - return - } else { - throw error - } - } - } - previewConversationPersonMakesAConversation() - ignorePromise(previewConversationTeam()) - }, - queueMetaHandle: () => { - // Watch the meta queue and take up to 10 items. Choose the last items first since they're likely still visible - const f = async () => { - const maxToUnboxAtATime = 10 - const ar = [...metaQueue] - const maybeUnbox = ar.slice(0, maxToUnboxAtATime) - metaQueue = new Set(ar.slice(maxToUnboxAtATime)) - const conversationIDKeys = untrustedConversationIDKeys(maybeUnbox) - if (conversationIDKeys.length) { - get().dispatch.unboxRows(conversationIDKeys) - } - if (metaQueue.size && conversationIDKeys.length) { - await timeoutPromise(100) - } - if (metaQueue.size) { - get().dispatch.queueMetaHandle() - } - } - ignorePromise(f()) - }, - queueMetaToRequest: ids => { - let added = false as boolean - untrustedConversationIDKeys(ids).forEach(k => { - if (!metaQueue.has(k)) { - added = true - metaQueue.add(k) - } - }) - if (added) { - // only unboxMore if something changed - get().dispatch.queueMetaHandle() - } else { - logger.info('skipping meta queue run, queue unchanged') - } - }, - refreshBotPublicCommands: username => { - set(s => { - s.botPublicCommands.delete(username) - }) - const f = async () => { - let res: T.RPCChat.ListBotCommandsLocalRes | undefined - try { - res = await T.RPCChat.localListPublicBotCommandsLocalRpcPromise({ - username, - }) - } catch (error) { - if (error instanceof RPCError) { - logger.info('refreshBotPublicCommands: failed to get public commands: ' + error.message) - set(s => { - s.botPublicCommands.set(username, {commands: [], loadError: true}) - }) - } - } - const commands = (res?.commands ?? []).reduce>((l, c) => { - l.push(c.name) - return l - }, []) - - set(s => { - s.botPublicCommands.set(username, {commands, loadError: false}) - }) - } - ignorePromise(f()) - }, - resetConversationErrored: () => { - set(s => { - s.createConversationError = undefined - }) - }, - resetState: () => { - set(s => ({ - ...s, - ...initialStore, - dispatch: s.dispatch, - staticConfig: s.staticConfig, - })) - // also blow away convoState - clearChatStores() - }, - setInboxNumSmallRows: (rows, ignoreWrite) => { - set(s => { - if (rows > 0) { - s.inboxNumSmallRows = rows - } - }) - if (ignoreWrite) { - return - } - const {inboxNumSmallRows} = get() - if (inboxNumSmallRows === undefined || inboxNumSmallRows <= 0) { - return - } - const f = async () => { - try { - await T.RPCGen.configGuiSetValueRpcPromise({ - path: 'ui.inboxSmallRows', - value: {i: inboxNumSmallRows, isNull: false}, - }) - } catch {} - } - ignorePromise(f()) - }, - setInfoPanelTab: tab => { - set(s => { - s.infoPanelSelectedTab = tab - }) - }, - setMaybeMentionInfo: (name, info) => { - set(s => { - const {maybeMentionMap} = s - maybeMentionMap.set(name, T.castDraft(info)) - }) - }, - setTrustedInboxHasLoaded: () => { - set(s => { - s.trustedInboxHasLoaded = true - }) - }, - toggleInboxSearch: enabled => { - set(s => { - const {inboxSearch} = s - if (enabled && !inboxSearch) { - s.inboxSearch = T.castDraft(makeInboxSearchInfo()) - } else if (!enabled && inboxSearch) { - s.inboxSearch = undefined - } - }) - const f = async () => { - const {inboxSearch} = get() - if (!inboxSearch) { - await T.RPCChat.localCancelActiveInboxSearchRpcPromise() - return - } - if (inboxSearch.nameStatus === 'initial') { - get().dispatch.inboxSearch('') - } - } - ignorePromise(f()) - }, - toggleSmallTeamsExpanded: () => { - set(s => { - s.smallTeamsExpanded = !s.smallTeamsExpanded - }) - }, - unboxRows: (ids, force) => { - // We want to unbox rows that have scroll into view - const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { - return - } - - // Get valid keys that we aren't already loading or have loaded - const conversationIDKeys = ids.reduce((arr: Array, id) => { - if (id && T.Chat.isValidConversationIDKey(id)) { - const cs = storeRegistry.getConvoState(id) - const trustedState = cs.meta.trustedState - if (force || (trustedState !== 'requesting' && trustedState !== 'trusted')) { - arr.push(id) - cs.dispatch.updateMeta({trustedState: 'requesting'}) - } - } - return arr - }, []) - - if (!conversationIDKeys.length) { - return - } - logger.info( - `unboxRows: unboxing len: ${conversationIDKeys.length} convs: ${conversationIDKeys.join(',')}` - ) - try { - await T.RPCChat.localRequestInboxUnboxRpcPromise({ - convIDs: conversationIDKeys.map(k => T.Chat.keyToConversationID(k)), - }) - } catch (error) { - if (error instanceof RPCError) { - logger.info(`unboxRows: failed ${error.desc}`) - } - } - } - ignorePromise(f()) - }, - updateCoinFlipStatus: statuses => { - set(s => { - const {flipStatusMap} = s - statuses.forEach(status => { - flipStatusMap.set(status.gameID, T.castDraft(status)) - }) - }) - }, - updateInboxLayout: str => { - set(s => { - try { - const {inboxHasLoaded} = s - const _layout = JSON.parse(str) as unknown - if (!_layout || typeof _layout !== 'object') { - console.log('Invalid layout?') - return - } - const layout = _layout as T.RPCChat.UIInboxLayout - - if (!isEqual(s.inboxLayout, layout)) { - s.inboxLayout = T.castDraft(layout) - } - s.inboxHasLoaded = !!layout - if (!inboxHasLoaded) { - // on first layout, initialize any drafts and muted status - // After the first layout, any other updates will come in the form of meta updates. - layout.smallTeams?.forEach(t => { - const cs = storeRegistry.getConvoState(t.convID) - cs.dispatch.updateFromUIInboxLayout(t) - }) - layout.bigTeams?.forEach(t => { - if (t.state === T.RPCChat.UIInboxBigTeamRowTyp.channel) { - const cs = storeRegistry.getConvoState(t.channel.convID) - cs.dispatch.updateFromUIInboxLayout(t.channel) - } - }) - } - } catch (e) { - logger.info('failed to JSON parse inbox layout: ' + e) - } - }) - }, - updateInfoPanel: (show, tab) => { - set(s => { - s.infoPanelShowing = show - s.infoPanelSelectedTab = tab - }) - }, - updateLastCoord: coord => { - set(s => { - s.lastCoord = coord - }) - const f = async () => { - const {accuracy, lat, lon} = coord - await T.RPCChat.localLocationUpdateRpcPromise({coord: {accuracy, lat, lon}}) - } - ignorePromise(f()) - }, - updateUserReacjis: userReacjis => { - set(s => { - const {skinTone, topReacjis} = userReacjis - s.userReacjis.skinTone = skinTone - // filter out non-simple emojis - s.userReacjis.topReacjis = - T.castDraft(topReacjis)?.filter(r => /^:[^:]+:$/.test(r.name)) ?? defaultTopReacjis - }) - }, - updatedGregor: items => { - const explodingItems = items.filter(i => - i.item.category.startsWith(Common.explodingModeGregorKeyPrefix) - ) - if (!explodingItems.length) { - // No conversations have exploding modes, clear out what is set - for (const s of chatStores.values()) { - s.getState().dispatch.setExplodingMode(0, true) - } - } else { - // logger.info('Got push state with some exploding modes') - explodingItems.forEach(i => { - try { - const {category, body} = i.item - const secondsString = uint8ArrayToString(body) - const seconds = parseInt(secondsString, 10) - if (isNaN(seconds)) { - logger.warn(`Got dirty exploding mode ${secondsString} for category ${category}`) - return - } - const _conversationIDKey = category.substring(Common.explodingModeGregorKeyPrefix.length) - const conversationIDKey = T.Chat.stringToConversationIDKey(_conversationIDKey) - storeRegistry.getConvoState(conversationIDKey).dispatch.setExplodingMode(seconds, true) - } catch (e) { - logger.info('Error parsing exploding' + e) - } - }) - } - - set(s => { - const blockButtons = items.some(i => i.item.category.startsWith(blockButtonsGregorPrefix)) - if (blockButtons || s.blockButtonsMap.size > 0) { - const shouldKeepExistingBlockButtons = new Map() - s.blockButtonsMap.forEach((_, teamID: string) => shouldKeepExistingBlockButtons.set(teamID, false)) - items - .filter(i => i.item.category.startsWith(blockButtonsGregorPrefix)) - .forEach(i => { - try { - const teamID = i.item.category.substring(blockButtonsGregorPrefix.length) - if (!s.blockButtonsMap.get(teamID)) { - const body = bodyToJSON(i.item.body) as {adder: string} - const adder = body.adder - s.blockButtonsMap.set(teamID, {adder}) - } else { - shouldKeepExistingBlockButtons.set(teamID, true) - } - } catch (e) { - logger.info('block buttons parse fail', e) - } - }) - shouldKeepExistingBlockButtons.forEach((keep, teamID) => { - if (!keep) { - s.blockButtonsMap.delete(teamID) - } - }) - } - }) - }, - } - return { - ...initialStore, - dispatch, - getBackCount: conversationIDKey => { - let count = 0 - chatStores.forEach(s => { - const {id, badge} = s.getState() - // only show sum of badges that aren't for the current conversation - if (id !== conversationIDKey) { - count += badge - } - }) - return count - }, - getBadgeHiddenCount: ids => { - let badgeCount = 0 - let hiddenCount = 0 - - chatStores.forEach(s => { - const {id, badge} = s.getState() - if (ids.has(id)) { - badgeCount -= badge - hiddenCount -= 1 - } - }) - - return {badgeCount, hiddenCount} - }, - getUnreadIndicies: ids => { - const unreadIndices: Map = new Map() - ids.forEach((cur, idx) => { - Array.from(chatStores.values()).some(s => { - const {id, badge} = s.getState() - if (id === cur && badge > 0) { - unreadIndices.set(idx, badge) - return true - } - return false - }) - }) - return unreadIndices - }, - } -}) - -import {type ChatProviderProps, ProviderScreen} from './convostate' -import type {GetOptionsRet} from '@/constants/types/router2' - -export function makeChatScreen>( - Component: COM, - options?: { - getOptions?: GetOptionsRet | ((props: ChatProviderProps>) => GetOptionsRet) - skipProvider?: boolean - canBeNullConvoID?: boolean - } -) { - return { - ...options, - screen: function Screen(p: ChatProviderProps>) { - const Comp = Component as any - return options?.skipProvider ? ( - - ) : ( - - - - ) - }, - } -} - -export * from './convostate' -export * from './common' -export * from './meta' -export * from './message' - -export { - noConversationIDKey, - pendingWaitingConversationIDKey, - pendingErrorConversationIDKey, - isValidConversationIDKey, - dummyConversationIDKey, -} from '../types/chat2/common' diff --git a/shared/constants/chat2/message.tsx b/shared/constants/chat2/message.tsx index c35c576d07e2..b0cdf7684d5b 100644 --- a/shared/constants/chat2/message.tsx +++ b/shared/constants/chat2/message.tsx @@ -1,14 +1,14 @@ // Message related constants import * as T from '../types' -import * as TeamsUtil from '../teams/util' -import type * as ConvoConstants from './convostate' +import * as TeamsUtil from '@/constants/teams' +import type * as ConvoConstants from '@/stores/convostate' import HiddenString from '@/util/hidden-string' import logger from '@/logger' -import type * as MessageTypes from '../types/chat2/message' +import type * as MessageTypes from '@/constants/types/chat2/message' import type {ServiceId} from 'util/platforms' -import {noConversationIDKey} from '../types/chat2/common' +import {noConversationIDKey} from '@/constants/types/chat2/common' import invert from 'lodash/invert' -import {isIOS, isMobile} from '../platform' +import {isIOS, isMobile} from '@/constants/platform' const noString = new HiddenString('') diff --git a/shared/constants/chat2/meta.tsx b/shared/constants/chat2/meta.tsx index 9adc5eda010f..1540e8f9879f 100644 --- a/shared/constants/chat2/meta.tsx +++ b/shared/constants/chat2/meta.tsx @@ -1,10 +1,11 @@ // Meta manages the metadata about a conversation. Participants, isMuted, reset people, etc. Things that drive the inbox import {shallowEqual} from '../utils' -import * as T from '../types' -import * as Teams from '../teams/util' +import * as T from '@/constants/types' +import * as Teams from '@/constants/teams' import * as Message from './message' import {base64ToUint8Array, uint8ArrayToHex} from 'uint8array-extras' -import {storeRegistry} from '../store-registry' +import {storeRegistry} from '@/stores/store-registry' +import {useCurrentUserState} from '@/stores/current-user' const conversationMemberStatusToMembershipType = (m: T.RPCChat.ConversationMemberStatus) => { switch (m) { @@ -40,9 +41,9 @@ export const unverifiedInboxUIItemToConversationMeta = ( // We only treat implicit adhoc teams as having resetParticipants const resetParticipants: Set = new Set( i.localMetadata && - (i.membersType === T.RPCChat.ConversationMembersType.impteamnative || - i.membersType === T.RPCChat.ConversationMembersType.impteamupgrade) && - i.localMetadata.resetParticipants + (i.membersType === T.RPCChat.ConversationMembersType.impteamnative || + i.membersType === T.RPCChat.ConversationMembersType.impteamupgrade) && + i.localMetadata.resetParticipants ? i.localMetadata.resetParticipants : [] ) @@ -236,7 +237,7 @@ export const inboxUIItemToConversationMeta = ( const resetParticipants = new Set( (i.membersType === T.RPCChat.ConversationMembersType.impteamnative || i.membersType === T.RPCChat.ConversationMembersType.impteamupgrade) && - i.resetParticipants + i.resetParticipants ? i.resetParticipants : [] ) @@ -263,8 +264,8 @@ export const inboxUIItemToConversationMeta = ( const conversationIDKey = T.Chat.stringToConversationIDKey(i.convID) let pinnedMsg: T.Chat.PinnedMessageInfo | undefined if (i.pinnedMsg) { - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => storeRegistry.getConvoState(conversationIDKey).messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const message = Message.uiMessageToMessage( diff --git a/shared/constants/chat2/util.tsx b/shared/constants/chat2/util.tsx deleted file mode 100644 index a1e5b2c74fc6..000000000000 --- a/shared/constants/chat2/util.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterChatUIRpcPromise() - await T.RPCGen.delegateUiCtlRegisterLogUIRpcPromise() - console.log('Registered Chat UI') - } catch (error) { - console.warn('Error in registering Chat UI:', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.chat1ChatUiChatInboxFailed: - case EngineGen.chat1NotifyChatChatSetConvSettings: - case EngineGen.chat1NotifyChatChatAttachmentUploadStart: - case EngineGen.chat1NotifyChatChatPromptUnfurl: - case EngineGen.chat1NotifyChatChatPaymentInfo: - case EngineGen.chat1NotifyChatChatRequestInfo: - case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: - case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: - case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: - case EngineGen.chat1ChatUiChatCommandMarkdown: - case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: - case EngineGen.chat1ChatUiChatCommandStatus: - case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: - case EngineGen.chat1ChatUiChatGiphySearchResults: - case EngineGen.chat1NotifyChatChatParticipantsInfo: - case EngineGen.chat1ChatUiChatMaybeMentionUpdate: - case EngineGen.chat1NotifyChatChatConvUpdate: - case EngineGen.chat1ChatUiChatCoinFlipStatus: - case EngineGen.chat1NotifyChatChatThreadsStale: - case EngineGen.chat1NotifyChatChatSubteamRename: - case EngineGen.chat1NotifyChatChatTLFFinalize: - case EngineGen.chat1NotifyChatChatIdentifyUpdate: - case EngineGen.chat1ChatUiChatInboxUnverified: - case EngineGen.chat1NotifyChatChatInboxSyncStarted: - case EngineGen.chat1NotifyChatChatInboxSynced: - case EngineGen.chat1ChatUiChatInboxLayout: - case EngineGen.chat1NotifyChatChatInboxStale: - case EngineGen.chat1ChatUiChatInboxConversation: - case EngineGen.chat1NotifyChatNewChatActivity: - case EngineGen.chat1NotifyChatChatTypingUpdate: - case EngineGen.chat1NotifyChatChatSetConvRetention: - case EngineGen.chat1NotifyChatChatSetTeamRetention: - case EngineGen.keybase1NotifyBadgesBadgeState: - case EngineGen.keybase1GregorUIPushState: - { - storeRegistry.getState('chat').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/config/util.tsx b/shared/constants/config.tsx similarity index 96% rename from shared/constants/config/util.tsx rename to shared/constants/config.tsx index b44de040a866..954a6daddde8 100644 --- a/shared/constants/config/util.tsx +++ b/shared/constants/config.tsx @@ -1,5 +1,5 @@ import uniq from 'lodash/uniq' -import {runMode} from '../platform' +import {runMode} from './platform' // An ugly error message from the service that we'd like to rewrite ourselves. export const invalidPasswordErrorString = 'Bad password: Invalid password. Server rejected login attempt..' diff --git a/shared/constants/crypto/util.tsx b/shared/constants/crypto.tsx similarity index 98% rename from shared/constants/crypto/util.tsx rename to shared/constants/crypto.tsx index 6f88e37d8e57..459215e60c1f 100644 --- a/shared/constants/crypto/util.tsx +++ b/shared/constants/crypto.tsx @@ -1,4 +1,4 @@ -import {isMobile} from '../platform' +import {isMobile} from './platform' export const saltpackDocumentation = 'https://saltpack.org' export const inputDesktopMaxHeight = {maxHeight: '30%'} as const diff --git a/shared/constants/deeplinks.tsx b/shared/constants/deeplinks.tsx new file mode 100644 index 000000000000..1b207eff3219 --- /dev/null +++ b/shared/constants/deeplinks.tsx @@ -0,0 +1,265 @@ +import * as Tabs from './tabs' +import URL from 'url-parse' +import logger from '@/logger' +import * as T from '@/constants/types' +import {navigateAppend, switchTab} from './router2' +import {storeRegistry} from '@/stores/store-registry' +import {useChatState} from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useTeamsState} from '@/stores/teams' + +const prefix = 'keybase://' +export const linkFromConvAndMessage = (conv: string, messageID: number) => + `${prefix}chat/${conv}/${messageID}` + +const isTeamPageAction = (a?: string): a is TeamPageAction => { + switch (a) { + case 'add_or_invite': + case 'manage_settings': + case 'join': + return true + default: + return false + } +} + +type TeamPageAction = 'add_or_invite' | 'manage_settings' | 'join' + +// This logic is copied from go/protocol/keybase1/extras.go. +const validTeamnamePart = (s: string): boolean => { + if (s.length < 2 || s.length > 16) { + return false + } + + return /^([a-zA-Z0-9][a-zA-Z0-9_]?)+$/.test(s) +} + +const validTeamname = (s: string) => s.split('.').every(validTeamnamePart) +const handleShowUserProfileLink = (username: string) => { + switchTab(Tabs.peopleTab) + useProfileState.getState().dispatch.showUserProfile(username) +} + +const isKeybaseIoUrl = (url: URL) => { + const {protocol} = url + if (protocol !== 'http:' && protocol !== 'https:') return false + if (url.username || url.password) return false + const {hostname} = url + if (hostname !== 'keybase.io' && hostname !== 'www.keybase.io') return false + const {port} = url + if (port) { + if (protocol === 'http:' && port !== '80') return false + if (protocol === 'https:' && port !== '443') return false + } + return true +} + +const urlToUsername = (url: URL) => { + if (!isKeybaseIoUrl(url)) { + return null + } + // Adapted username regexp (see libkb/checkers.go) with a leading /, an + // optional trailing / and a dash for custom links. + const match = url.pathname.match(/^\/((?:[a-zA-Z0-9][a-zA-Z0-9_-]?)+)\/?$/) + if (!match) { + return null + } + const usernameMatch = match[1] + if (!usernameMatch || usernameMatch.length < 2 || usernameMatch.length > 16) { + return null + } + // Ignore query string and hash parameters. + return usernameMatch.toLowerCase() +} + +const urlToTeamDeepLink = (url: URL) => { + if (!isKeybaseIoUrl(url)) { + return null + } + // Similar regexp to username but allow `.` for subteams + const match = url.pathname.match(/^\/team\/((?:[a-zA-Z0-9][a-zA-Z0-9_.-]?)+)\/?$/) + if (!match) { + return null + } + const teamName = match[1] + if (!teamName || teamName.length < 2 || teamName.length > 255) { + return null + } + // `url.query` has a wrong type in @types/url-parse. It's a `string` in the + // code, but @types claim it's a {[k: string]: string | undefined}. + const queryString = url.query as unknown as string + // URLSearchParams is not available in react-native. See if any of recognized + // query parameters is passed using regular expressions. + const action = (['add_or_invite', 'manage_settings'] satisfies readonly TeamPageAction[]).find( + x => queryString.search(`[?&]applink=${x}([?&].+)?$`) !== -1 + ) + return {action, teamName} +} + +const handleTeamPageLink = (teamname: string, action?: TeamPageAction) => { + useTeamsState + .getState() + .dispatch.showTeamByName( + teamname, + action === 'manage_settings' ? 'settings' : undefined, + action === 'join' ? true : undefined, + action === 'add_or_invite' ? true : undefined + ) +} + +export const handleAppLink = (link: string) => { + if (link.startsWith('keybase://')) { + handleKeybaseLink(link.replace('keybase://', '')) + return + } else { + // Normal deeplink + const url = new URL(link) + const username = urlToUsername(url) + if (username === 'phone-app') { + const phoneState = useSettingsPhoneState.getState() + const phones = (phoneState as {phones?: Map}).phones + if (!phones || phones.size > 0) { + return + } + switchTab(Tabs.settingsTab) + navigateAppend('settingsAddPhone') + } else if (username && username !== 'app') { + handleShowUserProfileLink(username) + return + } + const teamLink = urlToTeamDeepLink(url) + if (teamLink) { + handleTeamPageLink(teamLink.teamName, teamLink.action) + return + } + } +} + +export const handleKeybaseLink = (link: string) => { + if (!link) return + const error = + "We couldn't read this link. The link might be bad, or your Keybase app might be out of date and needs to be updated." + const parts = link.split('/') + // List guaranteed to contain at least one elem. + switch (parts[0]) { + case 'profile': + if (parts[1] === 'new-proof' && (parts.length === 3 || parts.length === 4)) { + parts.length === 4 && parts[3] && useProfileState.getState().dispatch.showUserProfile(parts[3]) + useProfileState.getState().dispatch.addProof(parts[2]!, 'appLink') + return + } else if (parts[1] === 'show' && parts.length === 3) { + // Username is basically a team name part, we can use the same logic to + // validate deep link. + const username = parts[2]! + if (username.length && validTeamnamePart(username)) { + return handleShowUserProfileLink(username) + } + } + break + // Fall-through + case 'private': + case 'public': + case 'team': + try { + const decoded = decodeURIComponent(link) + switchTab(Tabs.fsTab) + navigateAppend({props: {path: `/keybase/${decoded}`}, selected: 'fsRoot'}) + return + } catch { + logger.warn("Coudn't decode KBFS URI") + return + } + case 'convid': + if (parts.length === 2) { + const conversationIDKey = parts[1] + if (conversationIDKey) { + storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('navChanged') + } + return + } + break + case 'chat': + if (parts.length === 2 || parts.length === 3) { + if (parts[1]!.includes('#')) { + const teamChat = parts[1]!.split('#') + if (teamChat.length !== 2) { + navigateAppend({props: {error}, selected: 'keybaseLinkError'}) + return + } + const [teamname, channelname] = teamChat + const _highlightMessageID = parseInt(parts[2]!, 10) + if (_highlightMessageID < 0) { + logger.warn(`invalid chat message id: ${_highlightMessageID}`) + return + } + + const highlightMessageID = T.Chat.numberToMessageID(_highlightMessageID) + const {previewConversation} = useChatState.getState().dispatch + previewConversation({ + channelname, + highlightMessageID, + reason: 'appLink', + teamname, + }) + return + } else { + const highlightMessageID = parseInt(parts[2]!, 10) + if (highlightMessageID < 0) { + logger.warn(`invalid chat message id: ${highlightMessageID}`) + return + } + const {previewConversation} = useChatState.getState().dispatch + previewConversation({ + highlightMessageID: T.Chat.numberToMessageID(highlightMessageID), + participants: parts[1]!.split(','), + reason: 'appLink', + }) + return + } + } + break + case 'team-page': // keybase://team-page/{team_name}/{manage_settings,add_or_invite}? + if (parts.length >= 2) { + const teamName = parts[1]! + if (teamName.length && validTeamname(teamName)) { + const actionPart = parts[2] + const action = isTeamPageAction(actionPart) ? actionPart : undefined + handleTeamPageLink(teamName, action) + return + } + } + break + case 'incoming-share': + // android needs to render first when coming back + setTimeout(() => { + navigateAppend('incomingShareNew') + }, 500) + return + case 'team-invite-link': + useTeamsState.getState().dispatch.openInviteLink(parts[1] ?? '', parts[2] || '') + return + case 'settingsPushPrompt': + navigateAppend('settingsPushPrompt') + return + case Tabs.teamsTab: + switchTab(Tabs.teamsTab) + return + case Tabs.fsTab: + switchTab(Tabs.fsTab) + return + case Tabs.chatTab: + switchTab(Tabs.chatTab) + return + case Tabs.peopleTab: + switchTab(Tabs.peopleTab) + return + case Tabs.settingsTab: + switchTab(Tabs.settingsTab) + return + default: + // Fall through to the error return below. + } + navigateAppend({props: {error}, selected: 'keybaseLinkError'}) +} + diff --git a/shared/constants/deeplinks/index.tsx b/shared/constants/deeplinks/index.tsx deleted file mode 100644 index 9dfdf5e6e749..000000000000 --- a/shared/constants/deeplinks/index.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import * as Crypto from '../crypto/util' -import * as Tabs from '../tabs' -import {isPathSaltpackEncrypted, isPathSaltpackSigned} from '@/util/path' -import * as Z from '@/util/zustand' -import * as EngineGen from '@/actions/engine-gen-gen' -import type HiddenString from '@/util/hidden-string' -import URL from 'url-parse' -import logger from '@/logger' -import * as T from '@/constants/types' -import {navigateAppend, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' - -const prefix = 'keybase://' -type Store = T.Immutable<{ - keybaseLinkError: string -}> -export const linkFromConvAndMessage = (conv: string, messageID: number) => - `${prefix}chat/${conv}/${messageID}` - -const isTeamPageAction = (a?: string): a is TeamPageAction => { - switch (a) { - case 'add_or_invite': - case 'manage_settings': - case 'join': - return true - default: - return false - } -} - -type TeamPageAction = 'add_or_invite' | 'manage_settings' | 'join' - -// This logic is copied from go/protocol/keybase1/extras.go. -const validTeamnamePart = (s: string): boolean => { - if (s.length < 2 || s.length > 16) { - return false - } - - return /^([a-zA-Z0-9][a-zA-Z0-9_]?)+$/.test(s) -} - -const validTeamname = (s: string) => s.split('.').every(validTeamnamePart) - -const initialStore: Store = { - keybaseLinkError: '', -} - -export interface State extends Store { - dispatch: { - handleAppLink: (link: string) => void - handleKeybaseLink: (link: string) => void - handleSaltPackOpen: (_path: string | HiddenString) => void - onEngineIncomingImpl: (action: EngineGen.Actions) => void - resetState: 'default' - setLinkError: (e: string) => void - } -} - -export const useDeepLinksState = Z.createZustand((set, get) => { - const handleShowUserProfileLink = (username: string) => { - switchTab(Tabs.peopleTab) - storeRegistry.getState('profile').dispatch.showUserProfile(username) - } - - const isKeybaseIoUrl = (url: URL) => { - const {protocol} = url - if (protocol !== 'http:' && protocol !== 'https:') return false - if (url.username || url.password) return false - const {hostname} = url - if (hostname !== 'keybase.io' && hostname !== 'www.keybase.io') return false - const {port} = url - if (port) { - if (protocol === 'http:' && port !== '80') return false - if (protocol === 'https:' && port !== '443') return false - } - return true - } - - const urlToUsername = (url: URL) => { - if (!isKeybaseIoUrl(url)) { - return null - } - // Adapted username regexp (see libkb/checkers.go) with a leading /, an - // optional trailing / and a dash for custom links. - const match = url.pathname.match(/^\/((?:[a-zA-Z0-9][a-zA-Z0-9_-]?)+)\/?$/) - if (!match) { - return null - } - const usernameMatch = match[1] - if (!usernameMatch || usernameMatch.length < 2 || usernameMatch.length > 16) { - return null - } - // Ignore query string and hash parameters. - return usernameMatch.toLowerCase() - } - - const urlToTeamDeepLink = (url: URL) => { - if (!isKeybaseIoUrl(url)) { - return null - } - // Similar regexp to username but allow `.` for subteams - const match = url.pathname.match(/^\/team\/((?:[a-zA-Z0-9][a-zA-Z0-9_.-]?)+)\/?$/) - if (!match) { - return null - } - const teamName = match[1] - if (!teamName || teamName.length < 2 || teamName.length > 255) { - return null - } - // `url.query` has a wrong type in @types/url-parse. It's a `string` in the - // code, but @types claim it's a {[k: string]: string | undefined}. - const queryString = url.query as unknown as string - // URLSearchParams is not available in react-native. See if any of recognized - // query parameters is passed using regular expressions. - const action = (['add_or_invite', 'manage_settings'] satisfies readonly TeamPageAction[]).find( - x => queryString.search(`[?&]applink=${x}([?&].+)?$`) !== -1 - ) - return {action, teamName} - } - - const handleTeamPageLink = (teamname: string, action?: TeamPageAction) => { - storeRegistry - .getState('teams') - .dispatch.showTeamByName( - teamname, - action === 'manage_settings' ? 'settings' : undefined, - action === 'join' ? true : undefined, - action === 'add_or_invite' ? true : undefined - ) - } - - const dispatch: State['dispatch'] = { - handleAppLink: link => { - if (link.startsWith('keybase://')) { - get().dispatch.handleKeybaseLink(link.replace('keybase://', '')) - return - } else { - // Normal deeplink - const url = new URL(link) - const username = urlToUsername(url) - if (username === 'phone-app') { - const phones = storeRegistry.getState('settings-phone').phones - if (!phones || phones.size > 0) { - return - } - switchTab(Tabs.settingsTab) - navigateAppend('settingsAddPhone') - } else if (username && username !== 'app') { - handleShowUserProfileLink(username) - return - } - const teamLink = urlToTeamDeepLink(url) - if (teamLink) { - handleTeamPageLink(teamLink.teamName, teamLink.action) - return - } - } - }, - handleKeybaseLink: link => { - if (!link) return - const error = - "We couldn't read this link. The link might be bad, or your Keybase app might be out of date and needs to be updated." - const parts = link.split('/') - // List guaranteed to contain at least one elem. - switch (parts[0]) { - case 'profile': - if (parts[1] === 'new-proof' && (parts.length === 3 || parts.length === 4)) { - parts.length === 4 && - parts[3] && - storeRegistry.getState('profile').dispatch.showUserProfile(parts[3]) - storeRegistry.getState('profile').dispatch.addProof(parts[2]!, 'appLink') - return - } else if (parts[1] === 'show' && parts.length === 3) { - // Username is basically a team name part, we can use the same logic to - // validate deep link. - const username = parts[2]! - if (username.length && validTeamnamePart(username)) { - return handleShowUserProfileLink(username) - } - } - break - // Fall-through - case 'private': - case 'public': - case 'team': - try { - const decoded = decodeURIComponent(link) - switchTab(Tabs.fsTab) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {path: `/keybase/${decoded}`}, selected: 'fsRoot'}) - return - } catch { - logger.warn("Coudn't decode KBFS URI") - return - } - case 'convid': - if (parts.length === 2) { - const conversationIDKey = parts[1] - if (conversationIDKey) { - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('navChanged') - } - return - } - break - case 'chat': - if (parts.length === 2 || parts.length === 3) { - if (parts[1]!.includes('#')) { - const teamChat = parts[1]!.split('#') - if (teamChat.length !== 2) { - get().dispatch.setLinkError(error) - navigateAppend('keybaseLinkError') - return - } - const [teamname, channelname] = teamChat - const _highlightMessageID = parseInt(parts[2]!, 10) - if (_highlightMessageID < 0) { - logger.warn(`invalid chat message id: ${_highlightMessageID}`) - return - } - - const highlightMessageID = T.Chat.numberToMessageID(_highlightMessageID) - const {previewConversation} = storeRegistry.getState('chat').dispatch - previewConversation({ - channelname, - highlightMessageID, - reason: 'appLink', - teamname, - }) - return - } else { - const highlightMessageID = parseInt(parts[2]!, 10) - if (highlightMessageID < 0) { - logger.warn(`invalid chat message id: ${highlightMessageID}`) - return - } - const {previewConversation} = storeRegistry.getState('chat').dispatch - previewConversation({ - highlightMessageID: T.Chat.numberToMessageID(highlightMessageID), - participants: parts[1]!.split(','), - reason: 'appLink', - }) - return - } - } - break - case 'team-page': // keybase://team-page/{team_name}/{manage_settings,add_or_invite}? - if (parts.length >= 2) { - const teamName = parts[1]! - if (teamName.length && validTeamname(teamName)) { - const actionPart = parts[2] - const action = isTeamPageAction(actionPart) ? actionPart : undefined - handleTeamPageLink(teamName, action) - return - } - } - break - case 'incoming-share': - // android needs to render first when coming back - setTimeout(() => { - const selectedConversationIDKey = parts[1] - ? T.Chat.stringToConversationIDKey(parts[1]) - : undefined - navigateAppend({ - props: selectedConversationIDKey ? {selectedConversationIDKey} : {}, - selected: 'incomingShareNew', - }) - }, 500) - return - case 'team-invite-link': - storeRegistry.getState('teams').dispatch.openInviteLink(parts[1] ?? '', parts[2] || '') - return - case 'settingsPushPrompt': - navigateAppend('settingsPushPrompt') - return - case Tabs.teamsTab: - switchTab(Tabs.teamsTab) - return - case Tabs.fsTab: - switchTab(Tabs.fsTab) - return - case Tabs.chatTab: - switchTab(Tabs.chatTab) - return - case Tabs.peopleTab: - switchTab(Tabs.peopleTab) - return - case Tabs.settingsTab: - switchTab(Tabs.settingsTab) - return - default: - // Fall through to the error return below. - } - get().dispatch.setLinkError(error) - navigateAppend('keybaseLinkError') - }, - handleSaltPackOpen: _path => { - const path = typeof _path === 'string' ? _path : _path.stringValue() - - if (!storeRegistry.getState('config').loggedIn) { - console.warn('Tried to open a saltpack file before being logged in') - return - } - let operation: T.Crypto.Operations | undefined - if (isPathSaltpackEncrypted(path)) { - operation = Crypto.Operations.Decrypt - } else if (isPathSaltpackSigned(path)) { - operation = Crypto.Operations.Verify - } else { - logger.warn( - 'Deeplink received saltpack file path not ending in ".encrypted.saltpack" or ".signed.saltpack"' - ) - return - } - storeRegistry.getState('crypto').dispatch.onSaltpackOpenFile(operation, path) - switchTab(Tabs.cryptoTab) - }, - - onEngineIncomingImpl: action => { - switch (action.type) { - case EngineGen.keybase1NotifyServiceHandleKeybaseLink: { - const {link, deferred} = action.payload.params - if (deferred && !link.startsWith('keybase://team-invite-link/')) { - return - } - get().dispatch.handleKeybaseLink(link) - break - } - default: - } - }, - resetState: 'default', - setLinkError: e => { - set(s => { - s.keybaseLinkError = e - }) - }, - } - return { - ...initialStore, - dispatch, - } -}) diff --git a/shared/constants/deeplinks/util.tsx b/shared/constants/deeplinks/util.tsx deleted file mode 100644 index 7d1d3078ac54..000000000000 --- a/shared/constants/deeplinks/util.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyServiceHandleKeybaseLink: - { - storeRegistry.getState('deeplinks').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/devices/util.tsx b/shared/constants/devices/util.tsx deleted file mode 100644 index 23b3694282b6..000000000000 --- a/shared/constants/devices/util.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -let loaded = false - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyBadgesBadgeState: - { - const {badgeState} = action.payload.params - const {newDevices, revokedDevices} = badgeState - const hasValue = (newDevices?.length ?? 0) + (revokedDevices?.length ?? 0) > 0 - if (loaded || hasValue) { - loaded = true - storeRegistry.getState('devices').dispatch.onEngineIncomingImpl(action) - } - } - break - default: - } -} diff --git a/shared/constants/engine/index.tsx b/shared/constants/engine/index.tsx deleted file mode 100644 index e2d651083c6b..000000000000 --- a/shared/constants/engine/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' -import type * as EngineGen from '@/actions/engine-gen-gen' -import * as ArchiveUtil from '../archive/util' -import * as AutoResetUtil from '../autoreset/util' -import * as BotsUtil from '../bots/util' -import * as ChatUtil from '../chat2/util' -import * as DeepLinksUtil from '../deeplinks/util' -import * as DevicesUtil from '../devices/util' -import * as FSUtil from '../fs/util' -import * as GitUtil from '../git/util' -import * as NotifUtil from '../notifications/util' -import * as PeopleUtil from '../people/util' -import * as PinentryUtil from '../pinentry/util' -import * as SettingsUtil from '../settings/util' -import * as SignupUtil from '../signup/util' -import * as TeamsUtil from '../teams/util' -import * as TrackerUtil from '../tracker2/util' -import * as UnlockFoldersUtil from '../unlock-folders/util' -import * as UsersUtil from '../users/util' - -type Store = object -const initialStore: Store = {} - -export interface State extends Store { - dispatch: { - onEngineConnected: () => void - onEngineDisconnected: () => void - onEngineIncoming: (action: EngineGen.Actions) => void - resetState: () => void - } -} - -export const useEngineState = Z.createZustand(set => { - let incomingTimeout: NodeJS.Timeout - const dispatch: State['dispatch'] = { - onEngineConnected: () => { - ChatUtil.onEngineConnected() - storeRegistry.getState('config').dispatch.onEngineConnected() - NotifUtil.onEngineConnected() - PeopleUtil.onEngineConnected() - PinentryUtil.onEngineConnected() - TrackerUtil.onEngineConnected() - UnlockFoldersUtil.onEngineConnected() - }, - onEngineDisconnected: () => { - storeRegistry.getState('config').dispatch.onEngineDisonnected() - }, - onEngineIncoming: action => { - // defer a frame so its more like before - incomingTimeout = setTimeout(() => { - // we delegate to these utils so we don't need to load stores that we don't need yet - ArchiveUtil.onEngineIncoming(action) - AutoResetUtil.onEngineIncoming(action) - BotsUtil.onEngineIncoming(action) - ChatUtil.onEngineIncoming(action) - storeRegistry.getState('config').dispatch.dynamic.onEngineIncomingDesktop?.(action) - storeRegistry.getState('config').dispatch.dynamic.onEngineIncomingNative?.(action) - storeRegistry.getState('config').dispatch.onEngineIncoming(action) - DeepLinksUtil.onEngineIncoming(action) - DevicesUtil.onEngineIncoming(action) - FSUtil.onEngineIncoming(action) - GitUtil.onEngineIncoming(action) - NotifUtil.onEngineIncoming(action) - PeopleUtil.onEngineIncoming(action) - PinentryUtil.onEngineIncoming(action) - SettingsUtil.onEngineIncoming(action) - SignupUtil.onEngineIncoming(action) - TeamsUtil.onEngineIncoming(action) - TrackerUtil.onEngineIncoming(action) - UnlockFoldersUtil.onEngineIncoming(action) - UsersUtil.onEngineIncoming(action) - }, 0) - }, - resetState: () => { - set(s => ({...s, ...initialStore, dispatch: s.dispatch})) - clearTimeout(incomingTimeout) - }, - } - return { - ...initialStore, - dispatch, - } -}) diff --git a/shared/constants/fs.tsx b/shared/constants/fs.tsx new file mode 100644 index 000000000000..13b1a8580584 --- /dev/null +++ b/shared/constants/fs.tsx @@ -0,0 +1,772 @@ +import * as T from '@/constants/types' +import {isLinux, isMobile} from '@/constants/platform' +import {navigateAppend} from '@/constants/router2' + +// Prefetch Constants +const prefetchNotStarted: T.FS.PrefetchNotStarted = { + state: T.FS.PrefetchState.NotStarted, +} + +const prefetchComplete: T.FS.PrefetchComplete = { + state: T.FS.PrefetchState.Complete, +} + +export {prefetchNotStarted, prefetchComplete} + +export const navToPath = ( + // TODO: remove the second arg when we are done with migrating to nav2 + path: T.FS.Path +) => { + navigateAppend({props: {path}, selected: 'fsRoot'}) +} + +// Path Constants +export const defaultPath = T.FS.stringToPath('/keybase') + +// PathItem Constants +const pathItemMetadataDefault = { + lastModifiedTimestamp: 0, + lastWriter: '', + name: 'unknown', + prefetchStatus: prefetchNotStarted, + size: 0, + writable: false, +} + +export const emptyFolder: T.FS.FolderPathItem = { + ...pathItemMetadataDefault, + children: new Set(), + progress: T.FS.ProgressType.Pending, + type: T.FS.PathType.Folder, +} + +export const emptyFile: T.FS.FilePathItem = { + ...pathItemMetadataDefault, + type: T.FS.PathType.File, +} + +export const emptySymlink: T.FS.SymlinkPathItem = { + ...pathItemMetadataDefault, + linkTarget: '', + type: T.FS.PathType.Symlink, +} + +export const unknownPathItem: T.FS.UnknownPathItem = { + ...pathItemMetadataDefault, + type: T.FS.PathType.Unknown, +} + +// Factory Functions +export const unknownTlf = (() => { + const tlfSyncDisabled: T.FS.TlfSyncDisabled = { + mode: T.FS.TlfSyncMode.Disabled, + } + const makeConflictStateNormalView = ({ + localViewTlfPaths, + resolvingConflict, + stuckInConflict, + }: Partial): T.FS.ConflictStateNormalView => ({ + localViewTlfPaths: [...(localViewTlfPaths || [])], + resolvingConflict: resolvingConflict || false, + stuckInConflict: stuckInConflict || false, + type: T.FS.ConflictStateType.NormalView, + }) + const tlfNormalViewWithNoConflict = makeConflictStateNormalView({}) + const makeTlf = (p: Partial): T.FS.Tlf => { + const { + conflictState, + isFavorite, + isIgnored, + isNew, + name, + resetParticipants, + syncConfig, + teamId, + tlfMtime, + } = p + return { + conflictState: conflictState || tlfNormalViewWithNoConflict, + isFavorite: isFavorite || false, + isIgnored: isIgnored || false, + isNew: isNew || false, + name: name || '', + resetParticipants: [...(resetParticipants || [])], + syncConfig: syncConfig || tlfSyncDisabled, + teamId: teamId || '', + tlfMtime: tlfMtime || 0, + /* See comment in constants/types/fs.js + needsRekey: false, + waitingForParticipantUnlock: I.List(), + youCanUnlock: I.List(), + */ + } + } + return makeTlf({}) +})() + +// Empty/Default Objects +export const emptyNewFolder: T.FS.Edit = { + error: undefined, + name: 'New Folder', + originalName: 'New Folder', + parentPath: T.FS.stringToPath('/keybase'), + type: T.FS.EditType.NewFolder, +} + +export const emptySyncingFoldersProgress: T.FS.SyncingFoldersProgress = { + bytesFetched: 0, + bytesTotal: 0, + endEstimate: 0, + start: 0, +} + +export const emptyOverallSyncStatus: T.FS.OverallSyncStatus = { + diskSpaceStatus: T.FS.DiskSpaceStatus.Ok, + showingBanner: false, + syncingFoldersProgress: emptySyncingFoldersProgress, +} + +export const defaultPathUserSetting: T.FS.PathUserSetting = { + sort: T.FS.SortSetting.NameAsc, +} + +export const defaultTlfListPathUserSetting: T.FS.PathUserSetting = { + sort: T.FS.SortSetting.TimeAsc, +} + +export const emptyDownloadState: T.FS.DownloadState = { + canceled: false, + done: false, + endEstimate: 0, + error: '', + localPath: '', + progress: 0, +} + +export const emptyDownloadInfo: T.FS.DownloadInfo = { + filename: '', + isRegularDownload: false, + path: defaultPath, + startTime: 0, +} + +export const emptyPathItemActionMenu: T.FS.PathItemActionMenu = { + downloadID: undefined, + downloadIntent: undefined, + previousView: T.FS.PathItemActionMenuView.Root, + view: T.FS.PathItemActionMenuView.Root, +} + +export const emptySettings: T.FS.Settings = { + isLoading: false, + loaded: false, + sfmiBannerDismissed: false, + spaceAvailableNotificationThreshold: 0, + syncOnCellular: false, +} + +export const emptyPathInfo: T.FS.PathInfo = { + deeplinkPath: '', + platformAfterMountPath: '', +} + +export const emptyFileContext: T.FS.FileContext = { + contentType: '', + url: '', + viewType: T.RPCGen.GUIViewType.default, +} + +// Driver Status Constants +export const driverStatusUnknown: T.FS.DriverStatusUnknown = { + type: T.FS.DriverStatusType.Unknown, +} as const + +export const emptyDriverStatusEnabled: T.FS.DriverStatusEnabled = { + dokanOutdated: false, + dokanUninstallExecPath: undefined, + isDisabling: false, + type: T.FS.DriverStatusType.Enabled, +} as const + +export const emptyDriverStatusDisabled: T.FS.DriverStatusDisabled = { + isEnabling: false, + kextPermissionError: false, + type: T.FS.DriverStatusType.Disabled, +} as const + +export const defaultDriverStatus: T.FS.DriverStatus = isLinux ? emptyDriverStatusEnabled : driverStatusUnknown + +export const unknownKbfsDaemonStatus: T.FS.KbfsDaemonStatus = { + onlineStatus: T.FS.KbfsDaemonOnlineStatus.Unknown, + rpcStatus: T.FS.KbfsDaemonRpcStatus.Waiting, +} + +// Parsed Path Constants +const parsedPathRoot: T.FS.ParsedPathRoot = {kind: T.FS.PathKind.Root} + +const parsedPathPrivateList: T.FS.ParsedPathTlfList = { + kind: T.FS.PathKind.TlfList, + tlfType: T.FS.TlfType.Private, +} + +const parsedPathPublicList: T.FS.ParsedPathTlfList = { + kind: T.FS.PathKind.TlfList, + tlfType: T.FS.TlfType.Public, +} + +const parsedPathTeamList: T.FS.ParsedPathTlfList = { + kind: T.FS.PathKind.TlfList, + tlfType: T.FS.TlfType.Team, +} + +// Conversion Functions +export const pathToRPCPath = ( + path: T.FS.Path +): {PathType: T.RPCGen.PathType.kbfs; kbfs: T.RPCGen.KBFSPath} => ({ + PathType: T.RPCGen.PathType.kbfs, + kbfs: { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.fsGui, + path: T.FS.pathToString(path).substring('/keybase'.length) || '/', + }, +}) + +// Path/PathItem Utilities +export const pathTypeToTextType = (type: T.FS.PathType) => + type === T.FS.PathType.Folder ? 'BodySemibold' : 'Body' + +export const getPathItem = ( + pathItems: T.Immutable>, + path: T.Immutable +): T.Immutable => pathItems.get(path) || (unknownPathItem as T.FS.PathItem) + +export const getTlfPath = (path: T.FS.Path): T.FS.Path => { + const elems = T.FS.getPathElements(path) + return elems.length > 2 ? T.FS.pathConcat(T.FS.pathConcat(defaultPath, elems[1]!), elems[2]!) : undefined +} + +export const getTlfListFromType = ( + tlfs: T.Immutable, + tlfType: T.Immutable +): T.Immutable => { + switch (tlfType) { + case T.FS.TlfType.Private: + return tlfs.private + case T.FS.TlfType.Public: + return tlfs.public + case T.FS.TlfType.Team: + return tlfs.team + default: + return new Map() + } +} + +export const getTlfListAndTypeFromPath = ( + tlfs: T.Immutable, + path: T.Immutable +): T.Immutable<{ + tlfList: T.FS.TlfList + tlfType: T.FS.TlfType +}> => { + const visibility = T.FS.getPathVisibility(path) + switch (visibility) { + case T.FS.TlfType.Private: + case T.FS.TlfType.Public: + case T.FS.TlfType.Team: { + const tlfType: T.FS.TlfType = visibility + return {tlfList: getTlfListFromType(tlfs, tlfType), tlfType} + } + default: + return {tlfList: new Map(), tlfType: T.FS.TlfType.Private} + } +} + +export const getTlfFromPathInFavoritesOnly = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { + const elems = T.FS.getPathElements(path) + if (elems.length < 3) { + return unknownTlf + } + const {tlfList} = getTlfListAndTypeFromPath(tlfs, path) + return tlfList.get(elems[2]!) || unknownTlf +} + +export const getTlfFromPath = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { + const fromFavorites = getTlfFromPathInFavoritesOnly(tlfs, path) + return fromFavorites !== unknownTlf + ? fromFavorites + : tlfs.additionalTlfs.get(getTlfPath(path)) || unknownTlf +} + +export const getTlfFromTlfs = (tlfs: T.FS.Tlfs, tlfType: T.FS.TlfType, name: string): T.FS.Tlf => { + switch (tlfType) { + case T.FS.TlfType.Private: + return tlfs.private.get(name) || unknownTlf + case T.FS.TlfType.Public: + return tlfs.public.get(name) || unknownTlf + case T.FS.TlfType.Team: + return tlfs.team.get(name) || unknownTlf + default: + return unknownTlf + } +} + +export const tlfTypeAndNameToPath = (tlfType: T.FS.TlfType, name: string): T.FS.Path => + T.FS.stringToPath(`/keybase/${tlfType}/${name}`) + +export const getUploadedPath = (parentPath: T.FS.Path, localPath: string) => + T.FS.pathConcat(parentPath, T.FS.getLocalPathName(localPath)) + +export const pathsInSameTlf = (a: T.FS.Path, b: T.FS.Path): boolean => { + const elemsA = T.FS.getPathElements(a) + const elemsB = T.FS.getPathElements(b) + return elemsA.length >= 3 && elemsB.length >= 3 && elemsA[1] === elemsB[1] && elemsA[2] === elemsB[2] +} + +const slashKeybaseSlashLength = '/keybase/'.length +export const escapePath = (path: T.FS.Path): string => + 'keybase://' + + encodeURIComponent(T.FS.pathToString(path).slice(slashKeybaseSlashLength)).replace( + // We need to do this because otherwise encodeURIComponent would encode + // "/"s. + /%2F/g, + '/' + ) + +export const rebasePathToDifferentTlf = (path: T.FS.Path, newTlfPath: T.FS.Path) => + T.FS.pathConcat(newTlfPath, T.FS.getPathElements(path).slice(3).join('/')) + +export const isFolder = (path: T.FS.Path, pathItem: T.FS.PathItem) => + T.FS.getPathLevel(path) <= 3 || pathItem.type === T.FS.PathType.Folder + +export const isInTlf = (path: T.FS.Path) => T.FS.getPathLevel(path) > 2 + +export const hasPublicTag = (path: T.FS.Path): boolean => { + const publicPrefix = '/keybase/public/' + // The slash after public in `publicPrefix` prevents /keybase/public from counting. + return T.FS.pathToString(path).startsWith(publicPrefix) +} + +export const hasSpecialFileElement = (path: T.FS.Path): boolean => + T.FS.getPathElements(path).some(elem => elem.startsWith('.kbfs')) + +// Username/User Utilities +export const splitTlfIntoUsernames = (tlf: string): ReadonlyArray => + tlf.split(' ')[0]?.replace(/#/g, ',').split(',') ?? [] + +export const getUsernamesFromPath = (path: T.FS.Path): ReadonlyArray => { + const elems = T.FS.getPathElements(path) + return elems.length < 3 ? [] : splitTlfIntoUsernames(elems[2]!) +} + +export const usernameInPath = (username: string, path: T.FS.Path) => { + const elems = T.FS.getPathElements(path) + return elems.length >= 3 && elems[2]!.split(',').includes(username) +} + +const splitTlfIntoReadersAndWriters = ( + tlf: string +): { + readers?: Array + writers: Array +} => { + const [w, r] = tlf.split('#') + return { + readers: r ? r.split(',').filter(i => !!i) : undefined, + writers: w?.split(',').filter(i => !!i) ?? [], + } +} + +export const getUsernamesFromTlfName = (tlfName: string): Array => { + const split = splitTlfIntoReadersAndWriters(tlfName) + return split.writers.concat(split.readers || []) +} + +// TLF/List Utilities +export const computeBadgeNumberForTlfList = (tlfList: T.Immutable): number => + [...tlfList.values()].reduce((accumulator, tlf) => (tlfIsBadged(tlf) ? accumulator + 1 : accumulator), 0) + +export const computeBadgeNumberForAll = (tlfs: T.Immutable): number => + [T.FS.TlfType.Private, T.FS.TlfType.Public, T.FS.TlfType.Team] + .map(tlfType => computeBadgeNumberForTlfList(getTlfListFromType(tlfs, tlfType))) + .reduce((sum, count) => sum + count, 0) + +export const tlfIsBadged = (tlf: T.FS.Tlf) => !tlf.isIgnored && tlf.isNew + +export const tlfIsStuckInConflict = (tlf: T.FS.Tlf) => + tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict + +// Path Parsing +export const parsePath = (path: T.FS.Path): T.FS.ParsedPath => { + const elems = T.FS.getPathElements(path) + if (elems.length <= 1) { + return parsedPathRoot + } + switch (elems[1]) { + case 'private': + switch (elems.length) { + case 2: + return parsedPathPrivateList + case 3: + return { + kind: T.FS.PathKind.GroupTlf, + tlfName: elems[2]!, + tlfType: T.FS.TlfType.Private, + ...splitTlfIntoReadersAndWriters(elems[2]!), + } + default: + return { + kind: T.FS.PathKind.InGroupTlf, + rest: elems.slice(3), + tlfName: elems[2] ?? '', + tlfType: T.FS.TlfType.Private, + ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), + } + } + case 'public': + switch (elems.length) { + case 2: + return parsedPathPublicList + case 3: + return { + kind: T.FS.PathKind.GroupTlf, + tlfName: elems[2]!, + tlfType: T.FS.TlfType.Public, + ...splitTlfIntoReadersAndWriters(elems[2]!), + } + default: + return { + kind: T.FS.PathKind.InGroupTlf, + rest: elems.slice(3), + tlfName: elems[2] ?? '', + tlfType: T.FS.TlfType.Public, + ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), + } + } + case 'team': + switch (elems.length) { + case 2: + return parsedPathTeamList + case 3: + return { + kind: T.FS.PathKind.TeamTlf, + team: elems[2]!, + tlfName: elems[2]!, + tlfType: T.FS.TlfType.Team, + } + default: + return { + kind: T.FS.PathKind.InTeamTlf, + rest: elems.slice(3), + team: elems[2] ?? '', + tlfName: elems[2] ?? '', + tlfType: T.FS.TlfType.Team, + } + } + default: + return parsedPathRoot + } +} + +// Chat/Share Utilities +export const canChat = (path: T.FS.Path) => { + const parsedPath = parsePath(path) + switch (parsedPath.kind) { + case T.FS.PathKind.Root: + case T.FS.PathKind.TlfList: + return false + case T.FS.PathKind.GroupTlf: + case T.FS.PathKind.TeamTlf: + return true + case T.FS.PathKind.InGroupTlf: + case T.FS.PathKind.InTeamTlf: + return true + default: + return false + } +} + +export const isTeamPath = (path: T.FS.Path): boolean => { + const parsedPath = parsePath(path) + return parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team +} + +export const getChatTarget = (path: T.FS.Path, me: string): string => { + const parsedPath = parsePath(path) + if (parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team) { + return 'team conversation' + } + if (parsedPath.kind === T.FS.PathKind.GroupTlf || parsedPath.kind === T.FS.PathKind.InGroupTlf) { + if (parsedPath.writers.length === 1 && !parsedPath.readers && parsedPath.writers[0] === me) { + return 'yourself' + } + if (parsedPath.writers.length + (parsedPath.readers ? parsedPath.readers.length : 0) === 2) { + const notMe = parsedPath.writers.concat(parsedPath.readers || []).filter(u => u !== me) + if (notMe.length === 1) { + return notMe[0]! + } + } + return 'group conversation' + } + return 'conversation' +} + +export const getSharePathArrayDescription = (paths: ReadonlyArray): string => { + return !paths.length ? '' : paths.length === 1 ? T.FS.getPathName(paths[0]) : `${paths.length} items` +} + +export const getDestinationPickerPathName = (picker: T.FS.DestinationPicker): string => + picker.source.type === T.FS.DestinationPickerSource.MoveOrCopy + ? T.FS.getPathName(picker.source.path) + : picker.source.type === T.FS.DestinationPickerSource.IncomingShare + ? getSharePathArrayDescription( + picker.source.source + .map(({originalPath}) => (originalPath ? T.FS.getLocalPathName(originalPath) : '')) + .filter(Boolean) + ) + : '' + +// File/Download Utilities +export const humanReadableFileSize = (size: number) => { + const kib = 1024 + const mib = kib * kib + const gib = mib * kib + const tib = gib * kib + + if (!size) return '' + if (size >= tib) return `${Math.round(size / tib)} TB` + if (size >= gib) return `${Math.round(size / gib)} GB` + if (size >= mib) return `${Math.round(size / mib)} MB` + if (size >= kib) return `${Math.round(size / kib)} KB` + return `${size} B` +} + +export const humanizeBytes = (n: number, numDecimals: number): string => { + const kb = 1024 + const mb = kb * 1024 + const gb = mb * 1024 + + if (n < kb) { + return `${n} bytes` + } else if (n < mb) { + return `${(n / kb).toFixed(numDecimals)} KB` + } else if (n < gb) { + return `${(n / mb).toFixed(numDecimals)} MB` + } + return `${(n / gb).toFixed(numDecimals)} GB` +} + +export const humanizeBytesOfTotal = (n: number, d: number): string => { + const kb = 1024 + const mb = kb * 1024 + const gb = mb * 1024 + + if (d < kb) { + return `${n} of ${d} bytes` + } else if (d < mb) { + return `${(n / kb).toFixed(2)} of ${(d / kb).toFixed(2)} KB` + } else if (d < gb) { + return `${(n / mb).toFixed(2)} of ${(d / mb).toFixed(2)} MB` + } + return `${(n / gb).toFixed(2)} of ${(d / gb).toFixed(2)} GB` +} + +export const downloadIsOngoing = (dlState: T.FS.DownloadState) => + dlState !== emptyDownloadState && !dlState.error && !dlState.done && !dlState.canceled + +export const getDownloadIntent = ( + path: T.FS.Path, + downloads: T.FS.Downloads, + pathItemActionMenu: T.FS.PathItemActionMenu +): T.FS.DownloadIntent | undefined => { + const found = [...downloads.info].find(([_, info]) => info.path === path) + if (!found) { + return undefined + } + const [downloadID] = found + const dlState = downloads.state.get(downloadID) || emptyDownloadState + if (!downloadIsOngoing(dlState)) { + return undefined + } + if (pathItemActionMenu.downloadID === downloadID) { + return pathItemActionMenu.downloadIntent + } + return T.FS.DownloadIntent.None +} + +export const canSaveMedia = (pathItem: T.FS.PathItem, fileContext: T.FS.FileContext): boolean => { + if (pathItem.type !== T.FS.PathType.File || fileContext === emptyFileContext) { + return false + } + return ( + fileContext.viewType === T.RPCGen.GUIViewType.image || fileContext.viewType === T.RPCGen.GUIViewType.video + ) +} + +export const isOfflineUnsynced = ( + daemonStatus: T.FS.KbfsDaemonStatus, + pathItem: T.FS.PathItem, + path: T.FS.Path +) => + daemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline && + T.FS.getPathLevel(path) > 2 && + pathItem.prefetchStatus !== prefetchComplete + +// Status/Icon Utilities +export const getUploadIconForTlfType = ( + kbfsDaemonStatus: T.FS.KbfsDaemonStatus, + uploads: T.FS.Uploads, + tlfList: T.FS.TlfList, + tlfType: T.FS.TlfType +): T.FS.UploadIcon | undefined => { + if ( + [...tlfList].some( + ([_, tlf]) => + tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict + ) + ) { + return T.FS.UploadIcon.UploadingStuck + } + + const prefix = T.FS.pathToString(T.FS.getTlfTypePathFromTlfType(tlfType)) + if ( + [...uploads.syncingPaths].some(p => T.FS.pathToString(p).startsWith(prefix)) || + [...uploads.writingToJournal.keys()].some(p => T.FS.pathToString(p).startsWith(prefix)) + ) { + return kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline + ? T.FS.UploadIcon.AwaitingToUpload + : T.FS.UploadIcon.Uploading + } + + return undefined +} + +export const isPathEnabledForSync = (syncConfig: T.FS.TlfSyncConfig, path: T.FS.Path): boolean => { + switch (syncConfig.mode) { + case T.FS.TlfSyncMode.Disabled: + return false + case T.FS.TlfSyncMode.Enabled: + return true + case T.FS.TlfSyncMode.Partial: + // TODO: when we enable partial sync lookup, remember to deal with + // potential ".." traversal as well. + return syncConfig.enabledPaths.includes(path) + default: + return false + } +} + +export const getPathStatusIconInMergeProps = ( + kbfsDaemonStatus: T.FS.KbfsDaemonStatus, + tlf: T.Immutable, + pathItem: T.Immutable, + uploadingPaths: T.Immutable>, + path: T.Immutable +): T.FS.PathStatusIcon => { + // There's no upload or sync for local conflict view. + if (tlf.conflictState.type === T.FS.ConflictStateType.ManualResolvingLocalView) { + return T.FS.LocalConflictStatus + } + + // uploading state has higher priority + if (uploadingPaths.has(path)) { + // eslint-disable-next-line + return tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict + ? T.FS.UploadIcon.UploadingStuck + : kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline + ? T.FS.UploadIcon.AwaitingToUpload + : T.FS.UploadIcon.Uploading + } + if (!isPathEnabledForSync(tlf.syncConfig, path)) { + return T.FS.NonUploadStaticSyncStatus.OnlineOnly + } + + if (pathItem === unknownPathItem && tlf.syncConfig.mode !== T.FS.TlfSyncMode.Disabled) { + return T.FS.NonUploadStaticSyncStatus.Unknown + } + + // TODO: what about 'sync-error'? + + // We don't have an upload state, and sync is enabled for this path. + switch (pathItem.prefetchStatus.state) { + case T.FS.PrefetchState.NotStarted: + return T.FS.NonUploadStaticSyncStatus.AwaitingToSync + case T.FS.PrefetchState.Complete: + return T.FS.NonUploadStaticSyncStatus.Synced + case T.FS.PrefetchState.InProgress: { + if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { + return T.FS.NonUploadStaticSyncStatus.AwaitingToSync + } + const inProgress: T.FS.PrefetchInProgress = pathItem.prefetchStatus + if (inProgress.bytesTotal === 0) { + return T.FS.NonUploadStaticSyncStatus.AwaitingToSync + } + return inProgress.bytesFetched / inProgress.bytesTotal + } + default: + return T.FS.NonUploadStaticSyncStatus.Unknown + } +} + +export const getMainBannerType = ( + kbfsDaemonStatus: T.FS.KbfsDaemonStatus, + overallSyncStatus: T.FS.OverallSyncStatus +): T.FS.MainBannerType => { + if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { + return T.FS.MainBannerType.Offline + } else if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Trying) { + return T.FS.MainBannerType.TryingToConnect + } else if (overallSyncStatus.diskSpaceStatus === T.FS.DiskSpaceStatus.Error) { + return T.FS.MainBannerType.OutOfSpace + } else { + return T.FS.MainBannerType.None + } +} + +// Settings/Configuration Utilities +export const getPathUserSetting = ( + pathUserSettings: T.Immutable>, + path: T.Immutable +): T.FS.PathUserSetting => + pathUserSettings.get(path) || + (T.FS.getPathLevel(path) < 3 ? defaultTlfListPathUserSetting : defaultPathUserSetting) + +export const showSortSetting = ( + path: T.FS.Path, + pathItem: T.FS.PathItem, + kbfsDaemonStatus: T.FS.KbfsDaemonStatus +) => + !isMobile && + path !== defaultPath && + (T.FS.getPathLevel(path) === 2 || (pathItem.type === T.FS.PathType.Folder && !!pathItem.children.size)) && + !isOfflineUnsynced(kbfsDaemonStatus, pathItem, path) + +export const getSoftError = (softErrors: T.FS.SoftErrors, path: T.FS.Path): T.FS.SoftError | undefined => { + const pathError = softErrors.pathErrors.get(path) + if (pathError) { + return pathError + } + if (!softErrors.tlfErrors.size) { + return undefined + } + const tlfPath = getTlfPath(path) + return (tlfPath && softErrors.tlfErrors.get(tlfPath)) || undefined +} + +export const sfmiInfoLoaded = (settings: T.FS.Settings, driverStatus: T.FS.DriverStatus): boolean => + settings.loaded && driverStatus !== driverStatusUnknown + +export const hideOrDisableInDestinationPicker = ( + tlfType: T.FS.TlfType, + name: string, + username: string, + destinationPickerIndex?: number +) => typeof destinationPickerIndex === 'number' && tlfType === T.FS.TlfType.Public && name !== username + +// Other Utilities + +export const showIgnoreFolder = (path: T.FS.Path, username?: string): boolean => { + const elems = T.FS.getPathElements(path) + if (elems.length !== 3) { + return false + } + return ['public', 'private'].includes(elems[1]!) && elems[2]! !== username +} diff --git a/shared/constants/fs/common.native.tsx b/shared/constants/fs/common.native.tsx deleted file mode 100644 index c75025efc827..000000000000 --- a/shared/constants/fs/common.native.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import logger from '@/logger' -import {ignorePromise} from '../utils' -import {wrapErrors} from '@/util/debug' -import * as T from '../types' -import * as Styles from '@/styles' -import * as FS from '@/constants/fs' -import {launchImageLibraryAsync} from '@/util/expo-image-picker.native' -import {saveAttachmentToCameraRoll, showShareActionSheet} from '../platform-specific' -import {useFSState} from '.' - -export default function initNative() { - useFSState.setState(s => { - s.dispatch.dynamic.pickAndUploadMobile = wrapErrors( - (type: T.FS.MobilePickType, parentPath: T.FS.Path) => { - const f = async () => { - try { - const result = await launchImageLibraryAsync(type, true, true) - if (result.canceled) return - result.assets.map(r => - useFSState.getState().dispatch.upload(parentPath, Styles.unnormalizePath(r.uri)) - ) - } catch (e) { - FS.errorToActionOrThrow(e) - } - } - ignorePromise(f()) - } - ) - - s.dispatch.dynamic.finishedDownloadWithIntentMobile = wrapErrors( - (downloadID: string, downloadIntent: T.FS.DownloadIntent, mimeType: string) => { - const f = async () => { - const {downloads, dispatch} = useFSState.getState() - const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState - if (downloadState === FS.emptyDownloadState) { - logger.warn('missing download', downloadID) - return - } - const dismissDownload = dispatch.dismissDownload - if (downloadState.error) { - dispatch.redbar(downloadState.error) - dismissDownload(downloadID) - return - } - const {localPath} = downloadState - try { - switch (downloadIntent) { - case T.FS.DownloadIntent.CameraRoll: - await saveAttachmentToCameraRoll(localPath, mimeType) - dismissDownload(downloadID) - return - case T.FS.DownloadIntent.Share: - await showShareActionSheet({filePath: localPath, mimeType}) - dismissDownload(downloadID) - return - case T.FS.DownloadIntent.None: - return - default: - return - } - } catch (err) { - FS.errorToActionOrThrow(err) - } - } - ignorePromise(f()) - } - ) - }) -} diff --git a/shared/constants/fs/platform-specific.android.tsx b/shared/constants/fs/platform-specific.android.tsx deleted file mode 100644 index 8e7f3ff2a04b..000000000000 --- a/shared/constants/fs/platform-specific.android.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as T from '../types' -import {ignorePromise, wrapErrors} from '../utils' -import * as FS from '@/constants/fs' -import logger from '@/logger' -import nativeInit from './common.native' -import {useFSState} from '.' -import {androidAddCompleteDownload, fsCacheDir, fsDownloadDir} from 'react-native-kb' - -const finishedRegularDownloadIDs = new Set() - -export default function initPlatformSpecific() { - nativeInit() - - useFSState.setState(s => { - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { - const f = async () => { - await T.RPCGen.SimpleFSSimpleFSConfigureDownloadRpcPromise({ - // Android's cache dir is (when I tried) [app]/cache but Go side uses - // [app]/.cache by default, which can't be used for sharing to other apps. - cacheDirOverride: fsCacheDir, - downloadDirOverride: fsDownloadDir, - }) - } - ignorePromise(f()) - }) - // needs to be called, TODO could make this better - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged() - - s.dispatch.dynamic.finishedRegularDownloadMobile = wrapErrors( - (downloadID: string, mimeType: string) => { - const f = async () => { - // This is fired from a hook and can happen more than once per downloadID. - // So just deduplicate them here. This is small enough and won't happen - // constantly, so don't worry about clearing them. - if (finishedRegularDownloadIDs.has(downloadID)) { - return - } - finishedRegularDownloadIDs.add(downloadID) - - const {downloads} = useFSState.getState() - - const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState - const downloadInfo = downloads.info.get(downloadID) || FS.emptyDownloadInfo - if (downloadState === FS.emptyDownloadState || downloadInfo === FS.emptyDownloadInfo) { - logger.warn('missing download', downloadID) - return - } - if (downloadState.error) { - return - } - try { - await androidAddCompleteDownload({ - description: `Keybase downloaded ${downloadInfo.filename}`, - mime: mimeType, - path: downloadState.localPath, - showNotification: true, - title: downloadInfo.filename, - }) - } catch { - logger.warn('Failed to addCompleteDownload') - } - // No need to dismiss here as the download wrapper does it for Android. - } - ignorePromise(f()) - } - ) - }) -} diff --git a/shared/constants/fs/platform-specific.d.ts b/shared/constants/fs/platform-specific.d.ts deleted file mode 100644 index e5bdb364c59a..000000000000 --- a/shared/constants/fs/platform-specific.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare function initPlatformSpecific(): void -export default initPlatformSpecific diff --git a/shared/constants/fs/platform-specific.desktop.tsx b/shared/constants/fs/platform-specific.desktop.tsx deleted file mode 100644 index b9e372eded7c..000000000000 --- a/shared/constants/fs/platform-specific.desktop.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import * as T from '@/constants/types' -import {ignorePromise, wrapErrors} from '../utils' -import * as Constants from '../fs' -import * as Tabs from '../tabs' -import {isWindows, isLinux, pathSep, isDarwin} from '../platform.desktop' -import logger from '@/logger' -import * as Path from '@/util/path' -import KB2 from '@/util/electron.desktop' -import {uint8ArrayToHex} from 'uint8array-extras' -import {useFSState} from '.' -import {navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' - -const {openPathInFinder, openURL, getPathType, selectFilesToUploadDialog} = KB2.functions -const {darwinCopyToKBFSTempUploadFile, relaunchApp, uninstallKBFSDialog, uninstallDokanDialog} = KB2.functions -const {exitApp, windowsCheckMountFromOtherDokanInstall, installCachedDokan, uninstallDokan} = KB2.functions - -// _openPathInSystemFileManagerPromise opens `openPath` in system file manager. -// If isFolder is true, it just opens it. Otherwise, it shows it in its parent -// folder. This function does not check if the file exists, or try to convert -// KBFS paths. Caller should take care of those. -const _openPathInSystemFileManagerPromise = async (openPath: string, isFolder: boolean): Promise => - openPathInFinder?.(openPath, isFolder) - -const escapeBackslash = isWindows - ? (pathElem: string): string => - pathElem - .replace(/‰/g, '‰2030') - .replace(/([<>:"/\\|?*])/g, (_, c: Uint8Array) => '‰' + uint8ArrayToHex(c)) - : (pathElem: string): string => pathElem - -const _rebaseKbfsPathToMountLocation = (kbfsPath: T.FS.Path, mountLocation: string) => - Path.join(mountLocation, T.FS.getPathElements(kbfsPath).slice(1).map(escapeBackslash).join(pathSep)) - -const fuseStatusToUninstallExecPath = isWindows - ? (status: T.RPCGen.FuseStatus) => { - const field = status.status.fields?.find(({key}) => key === 'uninstallString') - return field?.value - } - : () => undefined - -const fuseStatusToActions = - (previousStatusType: T.FS.DriverStatusType) => (status: T.RPCGen.FuseStatus | undefined) => { - if (!status) { - useFSState.getState().dispatch.setDriverStatus(Constants.defaultDriverStatus) - return - } - - if (status.kextStarted) { - useFSState.getState().dispatch.setDriverStatus({ - ...Constants.emptyDriverStatusEnabled, - dokanOutdated: status.installAction === T.RPCGen.InstallAction.upgrade, - dokanUninstallExecPath: fuseStatusToUninstallExecPath(status), - }) - } else { - useFSState.getState().dispatch.setDriverStatus(Constants.emptyDriverStatusDisabled) - } - - if (status.kextStarted && previousStatusType === T.FS.DriverStatusType.Disabled) { - useFSState - .getState() - .dispatch.dynamic.openPathInSystemFileManagerDesktop?.(T.FS.stringToPath('/keybase')) - } - } - -const fuseInstallResultIsKextPermissionError = (result: T.RPCGen.InstallResult): boolean => - result.componentResults?.findIndex( - c => c.name === 'fuse' && c.exitCode === Constants.ExitCodeFuseKextPermissionError - ) !== -1 - -const driverEnableFuse = async (isRetry: boolean) => { - const result = await T.RPCGen.installInstallFuseRpcPromise() - if (fuseInstallResultIsKextPermissionError(result)) { - useFSState.getState().dispatch.driverKextPermissionError() - if (!isRetry) { - navigateAppend('kextPermission') - } - } else { - await T.RPCGen.installInstallKBFSRpcPromise() // restarts kbfsfuse - await T.RPCGen.kbfsMountWaitForMountsRpcPromise() - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() - } -} - -const uninstallKBFSConfirm = async () => { - const remove = await (uninstallKBFSDialog?.() ?? Promise.resolve(false)) - if (remove) { - useFSState.getState().dispatch.driverDisabling() - } -} - -const uninstallKBFS = async () => - T.RPCGen.installUninstallKBFSRpcPromise().then(() => { - // Restart since we had to uninstall KBFS and it's needed by the service (for chat) - relaunchApp?.() - exitApp?.(0) - }) - -const uninstallDokanConfirm = async () => { - const driverStatus = useFSState.getState().sfmi.driverStatus - if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { - return - } - if (!driverStatus.dokanUninstallExecPath) { - await uninstallDokanDialog?.() - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() - return - } - useFSState.getState().dispatch.driverDisabling() -} - -const onUninstallDokan = async () => { - const driverStatus = useFSState.getState().sfmi.driverStatus - if (driverStatus.type !== T.FS.DriverStatusType.Enabled) return - const execPath: string = driverStatus.dokanUninstallExecPath || '' - logger.info('Invoking dokan uninstaller', execPath) - try { - await uninstallDokan?.(execPath) - } catch {} - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() -} - -// Invoking the cached installer package has to happen from the topmost process -// or it won't be visible to the user. The service also does this to support command line -// operations. -const onInstallCachedDokan = async () => { - try { - await installCachedDokan?.() - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() - } catch (e) { - Constants.errorToActionOrThrow(e) - } -} - -const initPlatformSpecific = () => { - storeRegistry.getStore('config').subscribe((s, old) => { - if (s.appFocused === old.appFocused) return - useFSState.getState().dispatch.onChangedFocus(s.appFocused) - }) - - useFSState.setState(s => { - s.dispatch.dynamic.uploadFromDragAndDropDesktop = wrapErrors( - (parentPath: T.FS.Path, localPaths: string[]) => { - const {upload} = useFSState.getState().dispatch - const f = async () => { - if (isDarwin && darwinCopyToKBFSTempUploadFile) { - const dir = await T.RPCGen.SimpleFSSimpleFSMakeTempDirForUploadRpcPromise() - const lp = await Promise.all( - localPaths.map(async localPath => darwinCopyToKBFSTempUploadFile(dir, localPath)) - ) - lp.forEach(localPath => upload(parentPath, localPath)) - } else { - localPaths.forEach(localPath => upload(parentPath, localPath)) - } - } - ignorePromise(f()) - } - ) - - s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop = wrapErrors((localPath: string) => { - const f = async () => { - try { - if (getPathType) { - const pathType = await getPathType(localPath) - await _openPathInSystemFileManagerPromise(localPath, pathType === 'directory') - } - } catch (e) { - Constants.errorToActionOrThrow(e) - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.openPathInSystemFileManagerDesktop = wrapErrors((path: T.FS.Path) => { - const f = async () => { - const {sfmi, pathItems} = useFSState.getState() - return sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled && sfmi.directMountDir - ? _openPathInSystemFileManagerPromise( - _rebaseKbfsPathToMountLocation(path, sfmi.directMountDir), - ![T.FS.PathKind.InGroupTlf, T.FS.PathKind.InTeamTlf].includes(Constants.parsePath(path).kind) || - Constants.getPathItem(pathItems, path).type === T.FS.PathType.Folder - ).catch((e: unknown) => Constants.errorToActionOrThrow(e, path)) - : new Promise((resolve, reject) => { - if (sfmi.driverStatus.type !== T.FS.DriverStatusType.Enabled) { - // This usually indicates a developer error as - // openPathInSystemFileManager shouldn't be used when FUSE integration - // is not enabled. So just blackbar to encourage a log send. - reject(new Error('FUSE integration is not enabled')) - } else { - logger.warn('empty directMountDir') // if this happens it might be a race? - resolve() - } - }) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.refreshDriverStatusDesktop = wrapErrors(() => { - const f = async () => { - let status = await T.RPCGen.installFuseStatusRpcPromise({ - bundleVersion: '', - }) - if (isWindows && status.installStatus !== T.RPCGen.InstallStatus.installed) { - const m = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() - status = await (windowsCheckMountFromOtherDokanInstall?.(m, status) ?? Promise.resolve(status)) - } - fuseStatusToActions(useFSState.getState().sfmi.driverStatus.type)(status) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.refreshMountDirsDesktop = wrapErrors(() => { - const f = async () => { - const {sfmi, dispatch} = useFSState.getState() - const driverStatus = sfmi.driverStatus - if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { - return - } - const directMountDir = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() - const preferredMountDirs = await T.RPCGen.kbfsMountGetPreferredMountDirsRpcPromise() - dispatch.setDirectMountDir(directMountDir) - dispatch.setPreferredMountDirs(preferredMountDirs || []) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.setSfmiBannerDismissedDesktop = wrapErrors((dismissed: boolean) => { - const f = async () => { - await T.RPCGen.SimpleFSSimpleFSSetSfmiBannerDismissedRpcPromise({dismissed}) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.afterDriverEnabled = wrapErrors((isRetry: boolean) => { - const f = async () => { - useFSState.getState().dispatch.dynamic.setSfmiBannerDismissedDesktop?.(false) - if (isWindows) { - await onInstallCachedDokan() - } else { - await driverEnableFuse(isRetry) - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.afterDriverDisable = wrapErrors(() => { - const f = async () => { - useFSState.getState().dispatch.dynamic.setSfmiBannerDismissedDesktop?.(false) - if (isWindows) { - await uninstallDokanConfirm() - } else { - await uninstallKBFSConfirm() - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.afterDriverDisabling = wrapErrors(() => { - const f = async () => { - if (isWindows) { - await onUninstallDokan() - } else { - await uninstallKBFS() - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.openSecurityPreferencesDesktop = wrapErrors(() => { - const f = async () => { - await openURL?.('x-apple.systempreferences:com.apple.preference.security?General', {activate: true}) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.openFilesFromWidgetDesktop = wrapErrors((path: T.FS.Path) => { - storeRegistry.getState('config').dispatch.showMain() - if (path) { - Constants.makeActionForOpenPathInFilesTab(path) - } else { - navigateAppend(Tabs.fsTab) - } - }) - - s.dispatch.dynamic.openAndUploadDesktop = wrapErrors( - (type: T.FS.OpenDialogType, parentPath: T.FS.Path) => { - const f = async () => { - const localPaths = await (selectFilesToUploadDialog?.(type, parentPath ?? undefined) ?? - Promise.resolve([])) - localPaths.forEach(localPath => useFSState.getState().dispatch.upload(parentPath, localPath)) - } - ignorePromise(f()) - } - ) - - if (!isLinux) { - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { - const {kbfsDaemonStatus, dispatch} = useFSState.getState() - if (kbfsDaemonStatus.rpcStatus === T.FS.KbfsDaemonRpcStatus.Connected) { - dispatch.dynamic.refreshDriverStatusDesktop?.() - } - dispatch.dynamic.refreshMountDirsDesktop?.() - }) - // force call as it could have happened already - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged() - } - }) -} - -export default initPlatformSpecific diff --git a/shared/constants/fs/platform-specific.ios.tsx b/shared/constants/fs/platform-specific.ios.tsx deleted file mode 100644 index abfbc2d4b997..000000000000 --- a/shared/constants/fs/platform-specific.ios.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import nativeInit from './common.native' -export default nativeInit diff --git a/shared/constants/fs/util.tsx b/shared/constants/fs/util.tsx deleted file mode 100644 index 0ecea95b7a97..000000000000 --- a/shared/constants/fs/util.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import type * as T from '../types' -import {navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyFSFSOverallSyncStatusChanged: - case EngineGen.keybase1NotifyFSFSSubscriptionNotifyPath: - case EngineGen.keybase1NotifyFSFSSubscriptionNotify: - { - storeRegistry.getState('fs').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} - -export const makeActionForOpenPathInFilesTab = ( - // TODO: remove the second arg when we are done with migrating to nav2 - path: T.FS.Path -) => { - navigateAppend({props: {path}, selected: 'fsRoot'}) -} diff --git a/shared/constants/git/util.tsx b/shared/constants/git/util.tsx deleted file mode 100644 index c72418bcf735..000000000000 --- a/shared/constants/git/util.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -let loadedStore = false -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyBadgesBadgeState: - { - const {badgeState} = action.payload.params - const badges = new Set(badgeState.newGitRepoGlobalUniqueIDs) - // don't bother loading the store if no badges - if (loadedStore || badges.size) { - loadedStore = true - storeRegistry.getState('git').dispatch.onEngineIncomingImpl(action) - } - } - break - default: - } -} diff --git a/shared/constants/index.tsx b/shared/constants/index.tsx index b3359e15383d..7065a91ae687 100644 --- a/shared/constants/index.tsx +++ b/shared/constants/index.tsx @@ -1,10 +1,9 @@ export * from './platform' export * from './values' export * from './strings' -export {useRouterState, makeScreen} from './router2' -export * as Router2 from './router2' +export {useRouterState, makeScreen} from '@/stores/router2' +export * as Router2 from '@/stores/router2' export * as Tabs from './tabs' -export {useWaitingState} from './waiting' -export * as Waiting from './waiting' -export * as PlatformSpecific from './platform-specific' +export {useWaitingState} from '@/stores/waiting' +export * as Waiting from '@/stores/waiting' export * from './utils' diff --git a/shared/constants/init/index.d.ts b/shared/constants/init/index.d.ts new file mode 100644 index 000000000000..f08146c34c87 --- /dev/null +++ b/shared/constants/init/index.d.ts @@ -0,0 +1,7 @@ +// links all the stores together, stores never import this +import type * as EngineGen from '@/actions/engine-gen-gen' + +export declare function initPlatformListener(): void +export declare function onEngineConnected(): void +export declare function onEngineDisconnected(): void +export declare function onEngineIncoming(action: EngineGen.Actions): void diff --git a/shared/constants/init/index.desktop.tsx b/shared/constants/init/index.desktop.tsx new file mode 100644 index 000000000000..08d952d63514 --- /dev/null +++ b/shared/constants/init/index.desktop.tsx @@ -0,0 +1,583 @@ +// links all the stores together, stores never import this +import * as Chat from '@/stores/chat2' +import {ignorePromise} from '@/constants/utils' +import {useConfigState} from '@/stores/config' +import * as ConfigConstants from '@/stores/config' +import {useDaemonState} from '@/stores/daemon' +import {useFSState} from '@/stores/fs' +import {useProfileState} from '@/stores/profile' +import {useRouterState} from '@/stores/router2' +import * as EngineGen from '@/actions/engine-gen-gen' +import * as T from '@/constants/types' +import InputMonitor from '@/util/platform-specific/input-monitor.desktop' +import KB2 from '@/util/electron.desktop' +import logger from '@/logger' +import type {RPCError} from '@/util/errors' +import {getEngine} from '@/engine' +import {isLinux, isWindows, isDarwin, pathSep} from '@/constants/platform.desktop' +import {kbfsNotification} from '@/util/platform-specific/kbfs-notifications' +import {skipAppFocusActions} from '@/local-debug.desktop' +import NotifyPopup from '@/util/notify-popup' +import {noKBFSFailReason} from '@/constants/config' +import {initSharedSubscriptions, _onEngineIncoming} from './shared' +import {wrapErrors} from '@/util/debug' +import * as Constants from '@/constants/fs' +import * as Tabs from '@/constants/tabs' +import * as Path from '@/util/path' +import {uint8ArrayToHex} from 'uint8array-extras' +import {navigateAppend} from '@/constants/router2' +import {errorToActionOrThrow} from '@/stores/fs' +import {ExitCodeFuseKextPermissionError} from '@/constants/values' + +const {showMainWindow, activeChanged, requestWindowsStartService, ctlQuit, dumpNodeLogger} = KB2.functions +const {quitApp, exitApp, setOpenAtLogin, copyToClipboard} = KB2.functions +const {openPathInFinder, openURL, getPathType, selectFilesToUploadDialog} = KB2.functions +const {darwinCopyToKBFSTempUploadFile, relaunchApp, uninstallKBFSDialog, uninstallDokanDialog} = KB2.functions +const {windowsCheckMountFromOtherDokanInstall, installCachedDokan, uninstallDokan} = KB2.functions + +const dumpLogs = async (reason?: string) => { + await logger.dump() + await (dumpNodeLogger?.() ?? Promise.resolve([])) + // quit as soon as possible + if (reason === 'quitting through menu') { + ctlQuit?.() + } +} + +const maybePauseVideos = () => { + const {appFocused} = useConfigState.getState() + const videos = document.querySelectorAll('video') + const allVideos = Array.from(videos) + + allVideos.forEach(v => { + if (appFocused) { + if (v.hasAttribute('data-focus-paused')) { + if (v.paused) { + v.play() + .then(() => {}) + .catch(() => {}) + } + } + } else { + // only pause looping videos + if (!v.paused && v.hasAttribute('loop') && v.hasAttribute('autoplay')) { + v.setAttribute('data-focus-paused', 'true') + v.pause() + } + } + }) +} + +export const onEngineIncoming = (action: EngineGen.Actions) => { + _onEngineIncoming(action) + switch (action.type) { + case EngineGen.keybase1LogsendPrepareLogsend: { + const f = async () => { + const response = action.payload.response + try { + await dumpLogs() + } finally { + response.result() + } + } + ignorePromise(f()) + break + } + case EngineGen.keybase1NotifyAppExit: + console.log('App exit requested') + exitApp?.(0) + break + case EngineGen.keybase1NotifyFSFSActivity: + kbfsNotification(action.payload.params.notification, NotifyPopup) + break + case EngineGen.keybase1NotifyPGPPgpKeyInSecretStoreFile: { + const f = async () => { + try { + await T.RPCGen.pgpPgpStorageDismissRpcPromise() + } catch (err) { + console.warn('Error in sending pgpPgpStorageDismissRpc:', err) + } + } + ignorePromise(f()) + break + } + case EngineGen.keybase1NotifyServiceShutdown: { + const {code} = action.payload.params + if (isWindows && code !== (T.RPCGen.ExitCode.restart as number)) { + console.log('Quitting due to service shutdown with code: ', code) + // Quit just the app, not the service + quitApp?.() + } + break + } + + case EngineGen.keybase1LogUiLog: { + const {params} = action.payload + const {level, text} = params + logger.info('keybase.1.logUi.log:', params.text.data) + if (level >= T.RPCGen.LogLevel.error) { + NotifyPopup(text.data) + } + break + } + + case EngineGen.keybase1NotifySessionClientOutOfDate: { + const {upgradeTo, upgradeURI, upgradeMsg} = action.payload.params + const body = upgradeMsg || `Please update to ${upgradeTo} by going to ${upgradeURI}` + NotifyPopup('Client out of date!', {body}, 60 * 60) + // This is from the API server. Consider notifications from server always critical. + useConfigState + .getState() + .dispatch.setOutOfDate({critical: true, message: upgradeMsg, outOfDate: true, updating: false}) + break + } + default: + } +} + +// _openPathInSystemFileManagerPromise opens `openPath` in system file manager. +// If isFolder is true, it just opens it. Otherwise, it shows it in its parent +// folder. This function does not check if the file exists, or try to convert +// KBFS paths. Caller should take care of those. +const _openPathInSystemFileManagerPromise = async (openPath: string, isFolder: boolean): Promise => + openPathInFinder?.(openPath, isFolder) + +const escapeBackslash = isWindows + ? (pathElem: string): string => + pathElem + .replace(/‰/g, '‰2030') + .replace(/([<>:"/\\|?*])/g, (_, c: Uint8Array) => '‰' + uint8ArrayToHex(c)) + : (pathElem: string): string => pathElem + +const _rebaseKbfsPathToMountLocation = (kbfsPath: T.FS.Path, mountLocation: string) => + Path.join(mountLocation, T.FS.getPathElements(kbfsPath).slice(1).map(escapeBackslash).join(pathSep)) + +const fuseStatusToUninstallExecPath = isWindows + ? (status: T.RPCGen.FuseStatus) => { + const field = status.status.fields?.find(({key}) => key === 'uninstallString') + return field?.value + } + : () => undefined + +const fuseStatusToActions = + (previousStatusType: T.FS.DriverStatusType) => (status: T.RPCGen.FuseStatus | undefined) => { + if (!status) { + useFSState.getState().dispatch.setDriverStatus(Constants.defaultDriverStatus) + return + } + + if (status.kextStarted) { + useFSState.getState().dispatch.setDriverStatus({ + ...Constants.emptyDriverStatusEnabled, + dokanOutdated: status.installAction === T.RPCGen.InstallAction.upgrade, + dokanUninstallExecPath: fuseStatusToUninstallExecPath(status), + }) + } else { + useFSState.getState().dispatch.setDriverStatus(Constants.emptyDriverStatusDisabled) + } + + if (status.kextStarted && previousStatusType === T.FS.DriverStatusType.Disabled) { + useFSState + .getState() + .dispatch.defer.openPathInSystemFileManagerDesktop?.(T.FS.stringToPath('/keybase')) + } + } + +const fuseInstallResultIsKextPermissionError = (result: T.RPCGen.InstallResult): boolean => + result.componentResults?.findIndex( + c => c.name === 'fuse' && c.exitCode === ExitCodeFuseKextPermissionError + ) !== -1 + +const driverEnableFuse = async (isRetry: boolean) => { + const result = await T.RPCGen.installInstallFuseRpcPromise() + if (fuseInstallResultIsKextPermissionError(result)) { + useFSState.getState().dispatch.driverKextPermissionError() + if (!isRetry) { + navigateAppend('kextPermission') + } + } else { + await T.RPCGen.installInstallKBFSRpcPromise() // restarts kbfsfuse + await T.RPCGen.kbfsMountWaitForMountsRpcPromise() + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() + } +} + +const uninstallKBFSConfirm = async () => { + const remove = await (uninstallKBFSDialog?.() ?? Promise.resolve(false)) + if (remove) { + useFSState.getState().dispatch.driverDisabling() + } +} + +const uninstallKBFS = async () => + T.RPCGen.installUninstallKBFSRpcPromise().then(() => { + // Restart since we had to uninstall KBFS and it's needed by the service (for chat) + relaunchApp?.() + exitApp?.(0) + }) + +const uninstallDokanConfirm = async () => { + const driverStatus = useFSState.getState().sfmi.driverStatus + if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { + return + } + if (!driverStatus.dokanUninstallExecPath) { + await uninstallDokanDialog?.() + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() + return + } + useFSState.getState().dispatch.driverDisabling() +} + +const onUninstallDokan = async () => { + const driverStatus = useFSState.getState().sfmi.driverStatus + if (driverStatus.type !== T.FS.DriverStatusType.Enabled) return + const execPath: string = driverStatus.dokanUninstallExecPath || '' + logger.info('Invoking dokan uninstaller', execPath) + try { + await uninstallDokan?.(execPath) + } catch {} + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() +} + +// Invoking the cached installer package has to happen from the topmost process +// or it won't be visible to the user. The service also does this to support command line +// operations. +const onInstallCachedDokan = async () => { + try { + await installCachedDokan?.() + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() + } catch (e) { + errorToActionOrThrow(e) + } +} + +export const initPlatformListener = () => { + useConfigState.setState(s => { + s.dispatch.defer.dumpLogsNative = dumpLogs + s.dispatch.defer.showMainNative = wrapErrors(() => showMainWindow?.()) + s.dispatch.defer.copyToClipboard = wrapErrors((s: string) => copyToClipboard?.(s)) + s.dispatch.defer.onEngineConnectedDesktop = wrapErrors(() => { + // Introduce ourselves to the service + const f = async () => { + await T.RPCGen.configHelloIAmRpcPromise({details: KB2.constants.helloDetails}) + } + ignorePromise(f()) + }) + }) + + useConfigState.subscribe((s, old) => { + if (s.appFocused === old.appFocused) return + useFSState.getState().dispatch.onChangedFocus(s.appFocused) + }) + + useConfigState.subscribe((s, old) => { + if (s.loggedIn !== old.loggedIn) { + s.dispatch.osNetworkStatusChanged(navigator.onLine, 'notavailable', true) + } + + if (s.appFocused !== old.appFocused) { + maybePauseVideos() + } + + if (s.openAtLogin !== old.openAtLogin) { + const {openAtLogin} = s + const f = async () => { + if (__DEV__) { + console.log('onSetOpenAtLogin disabled for dev mode') + return + } else { + await T.RPCGen.configGuiSetValueRpcPromise({ + path: ConfigConstants.openAtLoginKey, + value: {b: openAtLogin, isNull: false}, + }) + } + if (isLinux || isWindows) { + const enabled = + (await T.RPCGen.ctlGetOnLoginStartupRpcPromise()) === T.RPCGen.OnLoginStartupStatus.enabled + if (enabled !== openAtLogin) { + try { + await T.RPCGen.ctlSetOnLoginStartupRpcPromise({enabled: openAtLogin}) + } catch (error_) { + const error = error_ as RPCError + logger.warn(`Error in sending ctlSetOnLoginStartup: ${error.message}`) + } + } + } else { + logger.info(`Login item settings changed! now ${openAtLogin ? 'on' : 'off'}`) + await setOpenAtLogin?.(openAtLogin) + } + } + ignorePromise(f()) + } + }) + + const handleWindowFocusEvents = () => { + const handle = (appFocused: boolean) => { + if (skipAppFocusActions) { + console.log('Skipping app focus actions!') + } else { + useConfigState.getState().dispatch.changedFocus(appFocused) + } + } + window.addEventListener('focus', () => handle(true)) + window.addEventListener('blur', () => handle(false)) + } + handleWindowFocusEvents() + + const setupReachabilityWatcher = () => { + window.addEventListener('online', () => + useConfigState.getState().dispatch.osNetworkStatusChanged(true, 'notavailable') + ) + window.addEventListener('offline', () => + useConfigState.getState().dispatch.osNetworkStatusChanged(false, 'notavailable') + ) + } + setupReachabilityWatcher() + + useDaemonState.subscribe((s, old) => { + if (s.handshakeVersion !== old.handshakeVersion) { + if (!isWindows) return + + const f = async () => { + const waitKey = 'pipeCheckFail' + const version = s.handshakeVersion + const {wait} = s.dispatch + wait(waitKey, version, true) + try { + logger.info('Checking RPC ownership') + if (KB2.functions.winCheckRPCOwnership) { + await KB2.functions.winCheckRPCOwnership() + } + wait(waitKey, version, false) + } catch (error_) { + // error will be logged in bootstrap check + getEngine().reset() + const error = error_ as RPCError + wait(waitKey, version, false, error.message || 'windows pipe owner fail', true) + } + } + ignorePromise(f()) + } + + if (s.handshakeState !== old.handshakeState && s.handshakeState === 'done') { + useConfigState.getState().dispatch.setStartupDetails({ + conversation: Chat.noConversationIDKey, + followUser: '', + link: '', + tab: undefined, + }) + } + }) + + if (isLinux) { + useConfigState.getState().dispatch.initUseNativeFrame() + } + useConfigState.getState().dispatch.initNotifySound() + useConfigState.getState().dispatch.initForceSmallNav() + useConfigState.getState().dispatch.initOpenAtLogin() + useConfigState.getState().dispatch.initAppUpdateLoop() + + useProfileState.setState(s => { + s.dispatch.editAvatar = () => { + useRouterState + .getState() + .dispatch.navigateAppend({props: {image: undefined}, selected: 'profileEditAvatar'}) + } + }) + + const initializeInputMonitor = () => { + const inputMonitor = new InputMonitor() + inputMonitor.notifyActive = (userActive: boolean) => { + if (skipAppFocusActions) { + console.log('Skipping app focus actions!') + } else { + useConfigState.getState().dispatch.setActive(userActive) + // let node thread save file + activeChanged?.(Date.now(), userActive) + } + } + } + initializeInputMonitor() + + useDaemonState.setState(s => { + s.dispatch.onRestartHandshakeNative = () => { + const {handshakeFailedReason} = useDaemonState.getState() + if (isWindows && handshakeFailedReason === noKBFSFailReason) { + requestWindowsStartService?.() + } + } + }) + + useFSState.setState(s => { + s.dispatch.defer.uploadFromDragAndDropDesktop = wrapErrors( + (parentPath: T.FS.Path, localPaths: string[]) => { + const {upload} = useFSState.getState().dispatch + const f = async () => { + if (isDarwin && darwinCopyToKBFSTempUploadFile) { + const dir = await T.RPCGen.SimpleFSSimpleFSMakeTempDirForUploadRpcPromise() + const lp = await Promise.all( + localPaths.map(async localPath => darwinCopyToKBFSTempUploadFile(dir, localPath)) + ) + lp.forEach(localPath => upload(parentPath, localPath)) + } else { + localPaths.forEach(localPath => upload(parentPath, localPath)) + } + } + ignorePromise(f()) + } + ) + + s.dispatch.defer.openLocalPathInSystemFileManagerDesktop = wrapErrors((localPath: string) => { + const f = async () => { + try { + if (getPathType) { + const pathType = await getPathType(localPath) + await _openPathInSystemFileManagerPromise(localPath, pathType === 'directory') + } + } catch (e) { + errorToActionOrThrow(e) + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.openPathInSystemFileManagerDesktop = wrapErrors((path: T.FS.Path) => { + const f = async () => { + const {sfmi, pathItems} = useFSState.getState() + return sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled && sfmi.directMountDir + ? _openPathInSystemFileManagerPromise( + _rebaseKbfsPathToMountLocation(path, sfmi.directMountDir), + ![T.FS.PathKind.InGroupTlf, T.FS.PathKind.InTeamTlf].includes(Constants.parsePath(path).kind) || + Constants.getPathItem(pathItems, path).type === T.FS.PathType.Folder + ).catch((e: unknown) => errorToActionOrThrow(e, path)) + : new Promise((resolve, reject) => { + if (sfmi.driverStatus.type !== T.FS.DriverStatusType.Enabled) { + // This usually indicates a developer error as + // openPathInSystemFileManager shouldn't be used when FUSE integration + // is not enabled. So just blackbar to encourage a log send. + reject(new Error('FUSE integration is not enabled')) + } else { + logger.warn('empty directMountDir') // if this happens it might be a race? + resolve() + } + }) + } + ignorePromise(f()) + }) + + s.dispatch.defer.refreshDriverStatusDesktop = wrapErrors(() => { + const f = async () => { + let status = await T.RPCGen.installFuseStatusRpcPromise({ + bundleVersion: '', + }) + if (isWindows && status.installStatus !== T.RPCGen.InstallStatus.installed) { + const m = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() + status = await (windowsCheckMountFromOtherDokanInstall?.(m, status) ?? Promise.resolve(status)) + } + fuseStatusToActions(useFSState.getState().sfmi.driverStatus.type)(status) + } + ignorePromise(f()) + }) + + s.dispatch.defer.refreshMountDirsDesktop = wrapErrors(() => { + const f = async () => { + const {sfmi, dispatch} = useFSState.getState() + const driverStatus = sfmi.driverStatus + if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { + return + } + const directMountDir = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() + const preferredMountDirs = await T.RPCGen.kbfsMountGetPreferredMountDirsRpcPromise() + dispatch.setDirectMountDir(directMountDir) + dispatch.setPreferredMountDirs(preferredMountDirs || []) + } + ignorePromise(f()) + }) + + s.dispatch.defer.setSfmiBannerDismissedDesktop = wrapErrors((dismissed: boolean) => { + const f = async () => { + await T.RPCGen.SimpleFSSimpleFSSetSfmiBannerDismissedRpcPromise({dismissed}) + } + ignorePromise(f()) + }) + + s.dispatch.defer.afterDriverEnabled = wrapErrors((isRetry: boolean) => { + const f = async () => { + useFSState.getState().dispatch.defer.setSfmiBannerDismissedDesktop?.(false) + if (isWindows) { + await onInstallCachedDokan() + } else { + await driverEnableFuse(isRetry) + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.afterDriverDisable = wrapErrors(() => { + const f = async () => { + useFSState.getState().dispatch.defer.setSfmiBannerDismissedDesktop?.(false) + if (isWindows) { + await uninstallDokanConfirm() + } else { + await uninstallKBFSConfirm() + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.afterDriverDisabling = wrapErrors(() => { + const f = async () => { + if (isWindows) { + await onUninstallDokan() + } else { + await uninstallKBFS() + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.openSecurityPreferencesDesktop = wrapErrors(() => { + const f = async () => { + await openURL?.('x-apple.systempreferences:com.apple.preference.security?General', {activate: true}) + } + ignorePromise(f()) + }) + + s.dispatch.defer.openFilesFromWidgetDesktop = wrapErrors((path: T.FS.Path) => { + useConfigState.getState().dispatch.showMain() + if (path) { + Constants.navToPath(path) + } else { + navigateAppend(Tabs.fsTab) + } + }) + + s.dispatch.defer.openAndUploadDesktop = wrapErrors( + (type: T.FS.OpenDialogType, parentPath: T.FS.Path) => { + const f = async () => { + const localPaths = await (selectFilesToUploadDialog?.(type, parentPath ?? undefined) ?? + Promise.resolve([])) + localPaths.forEach(localPath => useFSState.getState().dispatch.upload(parentPath, localPath)) + } + ignorePromise(f()) + } + ) + + if (!isLinux) { + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { + const {kbfsDaemonStatus, dispatch} = useFSState.getState() + if (kbfsDaemonStatus.rpcStatus === T.FS.KbfsDaemonRpcStatus.Connected) { + dispatch.defer.refreshDriverStatusDesktop?.() + } + dispatch.defer.refreshMountDirsDesktop?.() + }) + // force call as it could have happened already + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged() + } + }) + + initSharedSubscriptions() +} + +export {onEngineConnected, onEngineDisconnected} from './shared' diff --git a/shared/constants/platform-specific/index.native.tsx b/shared/constants/init/index.native.tsx similarity index 58% rename from shared/constants/platform-specific/index.native.tsx rename to shared/constants/init/index.native.tsx index 82d9536f9600..31e1b322cdd1 100644 --- a/shared/constants/platform-specific/index.native.tsx +++ b/shared/constants/init/index.native.tsx @@ -1,139 +1,54 @@ +// links all the stores together, stores never import this import {ignorePromise, neverThrowPromiseFunc, timeoutPromise} from '../utils' -import {storeRegistry} from '../store-registry' -import * as T from '../types' +import {useChatState} from '@/stores/chat2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' +import {useFSState} from '@/stores/fs' +import {useProfileState} from '@/stores/profile' +import {useRouterState} from '@/stores/router2' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import * as T from '@/constants/types' import * as Clipboard from 'expo-clipboard' import * as EngineGen from '@/actions/engine-gen-gen' import * as ExpoLocation from 'expo-location' import * as ExpoTaskManager from 'expo-task-manager' -import * as MediaLibrary from 'expo-media-library' -import * as Tabs from '../tabs' +import * as Tabs from '@/constants/tabs' import * as NetInfo from '@react-native-community/netinfo' import NotifyPopup from '@/util/notify-popup' -import {addNotificationRequest} from 'react-native-kb' import logger from '@/logger' -import {Alert, Linking, ActionSheetIOS} from 'react-native' -import {isIOS, isAndroid} from '../platform.native' +import {Alert, Linking} from 'react-native' +import {isAndroid} from '@/constants/platform.native' import {wrapErrors} from '@/util/debug' -import {getTab, getVisiblePath, logState} from '../router2' +import {getTab, getVisiblePath, logState} from '@/constants/router2' import {launchImageLibraryAsync} from '@/util/expo-image-picker.native' import {setupAudioMode} from '@/util/audio.native' import { + androidAddCompleteDownload, androidOpenSettings, - androidShare, - androidShareText, - androidUnlink, fsCacheDir, fsDownloadDir, androidAppColorSchemeChanged, guiConfig, shareListenersRegistered, } from 'react-native-kb' -import {initPushListener, getStartupDetailsFromInitialPush} from './push.native' +import {initPushListener, getStartupDetailsFromInitialPush} from './push-listener.native' +import {initSharedSubscriptions, _onEngineIncoming} from './shared' import type {ImageInfo} from '@/util/expo-image-picker.native' -import {noConversationIDKey} from '@/constants/types/chat2/common' - -export const requestPermissionsToWrite = async () => { - if (isAndroid) { - const p = await MediaLibrary.requestPermissionsAsync(false) - return p.granted ? Promise.resolve() : Promise.reject(new Error('Unable to acquire storage permissions')) - } - return Promise.resolve() -} - -export const requestLocationPermission = async (mode: T.RPCChat.UIWatchPositionPerm) => { - if (isIOS) { - logger.info('[location] Requesting location perms', mode) - switch (mode) { - case T.RPCChat.UIWatchPositionPerm.base: - { - const iosFGPerms = await ExpoLocation.requestForegroundPermissionsAsync() - if (iosFGPerms.ios?.scope === 'none') { - throw new Error('Please allow Keybase to access your location in the phone settings.') - } - } - break - case T.RPCChat.UIWatchPositionPerm.always: { - const iosBGPerms = await ExpoLocation.requestBackgroundPermissionsAsync() - if (iosBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { - throw new Error( - 'Please allow Keybase to access your location even if the app is not running for live location.' - ) - } - break - } - } - } else if (isAndroid) { - const androidBGPerms = await ExpoLocation.requestForegroundPermissionsAsync() - if (androidBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { - throw new Error('Unable to acquire location permissions') - } - } -} - -export async function saveAttachmentToCameraRoll(filePath: string, mimeType: string): Promise { - const fileURL = 'file://' + filePath - const saveType: 'video' | 'photo' = mimeType.startsWith('video') ? 'video' : 'photo' - const logPrefix = '[saveAttachmentToCameraRoll] ' - try { - try { - // see it we can keep going anyways, android perms are needed sometimes and sometimes not w/ 33 - await requestPermissionsToWrite() - } catch {} - logger.info(logPrefix + `Attempting to save as ${saveType}`) - await MediaLibrary.saveToLibraryAsync(fileURL) - logger.info(logPrefix + 'Success') - } catch (e) { - // This can fail if the user backgrounds too quickly, so throw up a local notification - // just in case to get their attention. - addNotificationRequest({ - body: `Failed to save ${saveType} to camera roll`, - id: Math.floor(Math.random() * 2 ** 32).toString(), - }).catch(() => {}) - logger.debug(logPrefix + 'failed to save: ' + e) - throw e - } finally { - try { - await androidUnlink(filePath) - } catch { - logger.warn('failed to unlink') - } - } -} - -export const showShareActionSheet = async (options: { - filePath?: string - message?: string - mimeType: string -}) => { - if (isIOS) { - return new Promise((resolve, reject) => { - ActionSheetIOS.showShareActionSheetWithOptions( - { - message: options.message, - url: options.filePath, - }, - reject, - resolve - ) - }) - } else { - if (!options.filePath && options.message) { - try { - await androidShareText(options.message, options.mimeType) - return {completed: true, method: ''} - } catch (e) { - throw new Error('Failed to share: ' + String(e)) - } - } +import {noConversationIDKey} from '../types/chat2/common' +import {getSelectedConversation} from '../chat2/common' +import {getConvoState} from '@/stores/convostate' +import { + requestLocationPermission, + saveAttachmentToCameraRoll, + showShareActionSheet, +} from '@/util/platform-specific/index.native' +import * as FS from '@/constants/fs' +import {errorToActionOrThrow} from '@/stores/fs' +import * as Styles from '@/styles' - try { - await androidShare(options.filePath ?? '', options.mimeType) - return {completed: true, method: ''} - } catch (e) { - throw new Error('Failed to share: ' + String(e)) - } - } -} +const finishedRegularDownloadIDs = new Set() const loadStartupDetails = async () => { const [routeState, initialUrl, push] = await Promise.all([ @@ -191,7 +106,7 @@ const loadStartupDetails = async () => { tab = '' } - storeRegistry.getState('config').dispatch.setStartupDetails({ + useConfigState.getState().dispatch.setStartupDetails({ conversation: conversation ?? noConversationIDKey, followUser, link, @@ -207,8 +122,42 @@ const loadStartupDetails = async () => { ) } +const locationTaskName = 'background-location-task' +let locationRefs = 0 +let madeBackgroundTask = false + +const ensureBackgroundTask = () => { + if (madeBackgroundTask) return + madeBackgroundTask = true + + ExpoTaskManager.defineTask(locationTaskName, async ({data, error}) => { + if (error) { + // check `error.message` for more details. + return Promise.resolve() + } + + if (!data) { + return Promise.resolve() + } + const d = data as {locations?: Array} + const locations = d.locations + if (!locations?.length) { + return Promise.resolve() + } + const pos = locations.at(-1) + const coord = { + accuracy: Math.floor(pos?.coords.accuracy ?? 0), + lat: pos?.coords.latitude ?? 0, + lon: pos?.coords.longitude ?? 0, + } + + useChatState.getState().dispatch.updateLastCoord(coord) + return Promise.resolve() + }) +} + const setPermissionDeniedCommandStatus = (conversationIDKey: T.Chat.ConversationIDKey, text: string) => { - storeRegistry.getConvoState(conversationIDKey).dispatch.setCommandStatusInfo({ + getConvoState(conversationIDKey).dispatch.setCommandStatusInfo({ actions: [T.RPCChat.UICommandStatusActionTyp.appsettings], displayText: text, displayType: T.RPCChat.UICommandStatusDisplayTyp.error, @@ -265,76 +214,35 @@ const onChatClearWatch = async () => { } } -const locationTaskName = 'background-location-task' -let locationRefs = 0 -let madeBackgroundTask = false - -const ensureBackgroundTask = () => { - if (madeBackgroundTask) return - madeBackgroundTask = true - - ExpoTaskManager.defineTask(locationTaskName, async ({data, error}) => { - if (error) { - // check `error.message` for more details. - return Promise.resolve() - } - - if (!data) { - return Promise.resolve() - } - const d = data as {locations?: Array} - const locations = d.locations - if (!locations?.length) { - return Promise.resolve() - } - const pos = locations.at(-1) - const coord = { - accuracy: Math.floor(pos?.coords.accuracy ?? 0), - lat: pos?.coords.latitude ?? 0, - lon: pos?.coords.longitude ?? 0, - } - - storeRegistry.getState('chat').dispatch.updateLastCoord(coord) - return Promise.resolve() - }) -} - -export const watchPositionForMap = async (conversationIDKey: T.Chat.ConversationIDKey) => { - try { - logger.info('[location] perms check due to map') - await requestLocationPermission(T.RPCChat.UIWatchPositionPerm.base) - } catch (_error) { - const error = _error as {message?: string} - logger.info('failed to get location perms: ' + error.message) - setPermissionDeniedCommandStatus(conversationIDKey, `Failed to access location. ${error.message}`) - return () => {} - } - - try { - const sub = await ExpoLocation.watchPositionAsync( - {accuracy: ExpoLocation.LocationAccuracy.Highest}, - location => { - const coord = { - accuracy: Math.floor(location.coords.accuracy ?? 0), - lat: location.coords.latitude, - lon: location.coords.longitude, - } - storeRegistry.getState('chat').dispatch.updateLastCoord(coord) +export const onEngineIncoming = (action: EngineGen.Actions) => { + _onEngineIncoming(action) + switch (action.type) { + case EngineGen.chat1ChatUiTriggerContactSync: + useSettingsContactsState.getState().dispatch.manageContactsCache() + break + case EngineGen.keybase1LogUiLog: { + const {params} = action.payload + const {level, text} = params + logger.info('keybase.1.logUi.log:', params.text.data) + if (level >= T.RPCGen.LogLevel.error) { + NotifyPopup(text.data) } - ) - return () => sub.remove() - } catch (_error) { - const error = _error as {message?: string} - logger.info('failed to get location: ' + error.message) - setPermissionDeniedCommandStatus(conversationIDKey, `Failed to access location. ${error.message}`) - return () => {} + break + } + case EngineGen.chat1ChatUiChatWatchPosition: + ignorePromise(onChatWatchPosition(action)) + break + case EngineGen.chat1ChatUiChatClearWatch: + ignorePromise(onChatClearWatch()) + break + default: } } export const initPlatformListener = () => { let _lastPersist = '' - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.persistRoute = wrapErrors((clear: boolean, immediate: boolean) => { + useConfigState.setState(s => { + s.dispatch.defer.persistRoute = wrapErrors((clear: boolean, immediate: boolean) => { const doClear = async () => { try { await T.RPCGen.configGuiSetValueRpcPromise({ @@ -344,7 +252,7 @@ export const initPlatformListener = () => { } catch {} } const doPersist = async () => { - if (!storeRegistry.getState('config').startup.loaded) { + if (!useConfigState.getState().startup.loaded) { return } let param = {} @@ -387,15 +295,9 @@ export const initPlatformListener = () => { ignorePromise(f()) } }) - - s.dispatch.dynamic.onEngineIncomingNative = wrapErrors((action: EngineGen.Actions) => { - switch (action.type) { - default: - } - }) }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.mobileAppState === old.mobileAppState) return let appFocused: boolean let logState: T.RPCGen.MobileAppState @@ -407,7 +309,7 @@ export const initPlatformListener = () => { case 'background': appFocused = false logState = T.RPCGen.MobileAppState.background - storeRegistry.getState('config').dispatch.dynamic.persistRoute?.(false, true) + useConfigState.getState().dispatch.defer.persistRoute?.(false, true) break case 'inactive': appFocused = false @@ -419,11 +321,17 @@ export const initPlatformListener = () => { } logger.info(`setting app state on service to: ${logState}`) - storeRegistry.getState('config').dispatch.changedFocus(appFocused) + s.dispatch.changedFocus(appFocused) + + if (appFocused && old.mobileAppState !== 'active') { + const {dispatch} = getConvoState(getSelectedConversation()) + dispatch.loadMoreMessages({reason: 'foregrounding'}) + dispatch.markThreadAsRead() + } }) - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.copyToClipboard = wrapErrors((s: string) => { + useConfigState.setState(s => { + s.dispatch.defer.copyToClipboard = wrapErrors((s: string) => { Clipboard.setStringAsync(s) .then(() => {}) .catch(() => {}) @@ -451,7 +359,7 @@ export const initPlatformListener = () => { } } - storeRegistry.getStore('daemon').subscribe((s, old) => { + useDaemonState.subscribe((s, old) => { const versionChanged = s.handshakeVersion !== old.handshakeVersion const stateChanged = s.handshakeState !== old.handshakeState const justBecameReady = stateChanged && s.handshakeState === 'done' && old.handshakeState !== 'done' @@ -461,11 +369,11 @@ export const initPlatformListener = () => { } }) - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.onFilePickerError = wrapErrors((error: Error) => { + useConfigState.setState(s => { + s.dispatch.defer.onFilePickerError = wrapErrors((error: Error) => { Alert.alert('Error', String(error)) }) - s.dispatch.dynamic.openAppStore = wrapErrors(() => { + s.dispatch.defer.openAppStore = wrapErrors(() => { Linking.openURL( isAndroid ? 'http://play.google.com/store/apps/details?id=io.keybase.ossifrage' @@ -474,7 +382,7 @@ export const initPlatformListener = () => { }) }) - storeRegistry.getStore('profile').setState(s => { + useProfileState.setState(s => { s.dispatch.editAvatar = () => { const f = async () => { try { @@ -486,30 +394,28 @@ export const initPlatformListener = () => { return acc }, undefined) if (!result.canceled && first) { - storeRegistry - .getState('router') + useRouterState + .getState() .dispatch.navigateAppend({props: {image: first}, selected: 'profileEditAvatar'}) } } catch (error) { - storeRegistry.getState('config').dispatch.filePickerError(new Error(String(error))) + useConfigState.getState().dispatch.filePickerError(new Error(String(error))) } } ignorePromise(f()) } }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.loggedIn === old.loggedIn) return const f = async () => { const {type} = await NetInfo.fetch() - storeRegistry - .getState('config') - .dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type, true) + s.dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type, true) } ignorePromise(f()) }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.networkStatus === old.networkStatus) return const type = s.networkStatus?.type if (!type) return @@ -523,8 +429,8 @@ export const initPlatformListener = () => { ignorePromise(f()) }) - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.showShareActionSheet = wrapErrors( + useConfigState.setState(s => { + s.dispatch.defer.showShareActionSheet = wrapErrors( (filePath: string, message: string, mimeType: string) => { const f = async () => { await showShareActionSheet({filePath, message, mimeType}) @@ -534,31 +440,30 @@ export const initPlatformListener = () => { ) }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.mobileAppState === old.mobileAppState) return if (s.mobileAppState === 'active') { // only reload on foreground - storeRegistry.getState('settings-contacts').dispatch.loadContactPermissions() + useSettingsContactsState.getState().dispatch.loadContactPermissions() } }) // Location if (isAndroid) { - storeRegistry.getStore('dark-mode').subscribe((s, old) => { + useDarkModeState.subscribe((s, old) => { if (s.darkModePreference === old.darkModePreference) return - const {darkModePreference} = storeRegistry.getState('dark-mode') - androidAppColorSchemeChanged(darkModePreference) + androidAppColorSchemeChanged(s.darkModePreference) }) } // we call this when we're logged in. let calledShareListenersRegistered = false - storeRegistry.getStore('router').subscribe((s, old) => { + useRouterState.subscribe((s, old) => { const next = s.navState const prev = old.navState if (next === prev) return - storeRegistry.getState('config').dispatch.dynamic.persistRoute?.(false, false) + useConfigState.getState().dispatch.defer.persistRoute?.(false, false) if (!calledShareListenersRegistered && logState().loggedIn) { calledShareListenersRegistered = true @@ -571,9 +476,7 @@ export const initPlatformListener = () => { initPushListener() NetInfo.addEventListener(({type}) => { - storeRegistry - .getState('config') - .dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type) + useConfigState.getState().dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type) }) const initAudioModes = () => { @@ -582,14 +485,14 @@ export const initPlatformListener = () => { initAudioModes() if (isAndroid) { - const daemonState = storeRegistry.getState('daemon') + const daemonState = useDaemonState.getState() if (daemonState.handshakeState === 'done' || daemonState.handshakeVersion > 0) { configureAndroidCacheDir() } } - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.openAppSettings = wrapErrors(() => { + useConfigState.setState(s => { + s.dispatch.defer.openAppSettings = wrapErrors(() => { const f = async () => { if (isAndroid) { androidOpenSettings() @@ -605,44 +508,137 @@ export const initPlatformListener = () => { } ignorePromise(f()) }) - - s.dispatch.dynamic.onEngineIncomingNative = wrapErrors((action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.chat1ChatUiTriggerContactSync: - storeRegistry.getState('settings-contacts').dispatch.manageContactsCache() - break - case EngineGen.keybase1LogUiLog: { - const {params} = action.payload - const {level, text} = params - logger.info('keybase.1.logUi.log:', params.text.data) - if (level >= T.RPCGen.LogLevel.error) { - NotifyPopup(text.data) - } - break - } - case EngineGen.chat1ChatUiChatWatchPosition: - ignorePromise(onChatWatchPosition(action)) - break - case EngineGen.chat1ChatUiChatClearWatch: - ignorePromise(onChatClearWatch()) - break - default: - } - }) }) - storeRegistry.getStore('router').setState(s => { - s.dispatch.dynamic.tabLongPress = wrapErrors((tab: string) => { + useRouterState.setState(s => { + s.dispatch.defer.tabLongPress = wrapErrors((tab: string) => { if (tab !== Tabs.peopleTab) return - const accountRows = storeRegistry.getState('config').configuredAccounts - const current = storeRegistry.getState('current-user').username + const accountRows = useConfigState.getState().configuredAccounts + const current = useCurrentUserState.getState().username const row = accountRows.find(a => a.username !== current && a.hasStoredSecret) if (row) { - storeRegistry.getState('config').dispatch.setUserSwitching(true) - storeRegistry.getState('config').dispatch.login(row.username, '') + useConfigState.getState().dispatch.setUserSwitching(true) + useConfigState.getState().dispatch.login(row.username, '') } }) }) - ignorePromise(storeRegistry.getState('fs').dispatch.setupSubscriptions()) + useFSState.setState(s => { + s.dispatch.defer.pickAndUploadMobile = wrapErrors( + (type: T.FS.MobilePickType, parentPath: T.FS.Path) => { + const f = async () => { + try { + const result = await launchImageLibraryAsync(type, true, true) + if (result.canceled) return + result.assets.map(r => + useFSState.getState().dispatch.upload(parentPath, Styles.unnormalizePath(r.uri)) + ) + } catch (e) { + errorToActionOrThrow(e) + } + } + ignorePromise(f()) + } + ) + + s.dispatch.defer.finishedDownloadWithIntentMobile = wrapErrors( + (downloadID: string, downloadIntent: T.FS.DownloadIntent, mimeType: string) => { + const f = async () => { + const {downloads, dispatch} = useFSState.getState() + const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState + if (downloadState === FS.emptyDownloadState) { + logger.warn('missing download', downloadID) + return + } + const dismissDownload = dispatch.dismissDownload + if (downloadState.error) { + dispatch.redbar(downloadState.error) + dismissDownload(downloadID) + return + } + const {localPath} = downloadState + try { + switch (downloadIntent) { + case T.FS.DownloadIntent.CameraRoll: + await saveAttachmentToCameraRoll(localPath, mimeType) + dismissDownload(downloadID) + return + case T.FS.DownloadIntent.Share: + await showShareActionSheet({filePath: localPath, mimeType}) + dismissDownload(downloadID) + return + case T.FS.DownloadIntent.None: + return + default: + return + } + } catch (err) { + errorToActionOrThrow(err) + } + } + ignorePromise(f()) + } + ) + }) + + if (isAndroid) { + useFSState.setState(s => { + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { + const f = async () => { + await T.RPCGen.SimpleFSSimpleFSConfigureDownloadRpcPromise({ + // Android's cache dir is (when I tried) [app]/cache but Go side uses + // [app]/.cache by default, which can't be used for sharing to other apps. + cacheDirOverride: fsCacheDir, + downloadDirOverride: fsDownloadDir, + }) + } + ignorePromise(f()) + }) + // needs to be called, TODO could make this better + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged() + + s.dispatch.defer.finishedRegularDownloadMobile = wrapErrors( + (downloadID: string, mimeType: string) => { + const f = async () => { + // This is fired from a hook and can happen more than once per downloadID. + // So just deduplicate them here. This is small enough and won't happen + // constantly, so don't worry about clearing them. + if (finishedRegularDownloadIDs.has(downloadID)) { + return + } + finishedRegularDownloadIDs.add(downloadID) + + const {downloads} = useFSState.getState() + + const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState + const downloadInfo = downloads.info.get(downloadID) || FS.emptyDownloadInfo + if (downloadState === FS.emptyDownloadState || downloadInfo === FS.emptyDownloadInfo) { + logger.warn('missing download', downloadID) + return + } + if (downloadState.error) { + return + } + try { + await androidAddCompleteDownload({ + description: `Keybase downloaded ${downloadInfo.filename}`, + mime: mimeType, + path: downloadState.localPath, + showNotification: true, + title: downloadInfo.filename, + }) + } catch { + logger.warn('Failed to addCompleteDownload') + } + // No need to dismiss here as the download wrapper does it for Android. + } + ignorePromise(f()) + } + ) + }) + } + + initSharedSubscriptions() } + +export {onEngineConnected, onEngineDisconnected} from './shared' diff --git a/shared/constants/platform-specific/push.native.tsx b/shared/constants/init/push-listener.native.tsx similarity index 87% rename from shared/constants/platform-specific/push.native.tsx rename to shared/constants/init/push-listener.native.tsx index 5da8dcdb800c..a30a3e2bfe0b 100644 --- a/shared/constants/platform-specific/push.native.tsx +++ b/shared/constants/init/push-listener.native.tsx @@ -1,7 +1,8 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise} from '../utils' +import * as T from '@/constants/types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' import logger from '@/logger' -import {isAndroid, isIOS} from '../platform' +import {handleAppLink} from '@/constants/deeplinks' +import {isAndroid, isIOS} from '@/constants/platform' import { getRegistrationToken, setApplicationIconBadgeNumber, @@ -9,8 +10,9 @@ import { getInitialNotification, removeAllPendingNotificationRequests, } from 'react-native-kb' -import {storeRegistry} from '../store-registry' -import {DeviceEventEmitter} from 'react-native' +import {useConfigState} from '@/stores/config' +import {useLogoutState} from '@/stores/logout' +import {usePushState} from '@/stores/push' type DataCommon = { userInteraction: boolean @@ -162,7 +164,7 @@ const getStartupDetailsFromInitialPush = async () => { export const initPushListener = () => { // Permissions - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.mobileAppState === old.mobileAppState) return // Only recheck on foreground, not background if (s.mobileAppState !== 'active') { @@ -170,21 +172,21 @@ export const initPushListener = () => { return } logger.debug(`[PushCheck] checking on foreground`) - storeRegistry - .getState('push') + usePushState + .getState() .dispatch.checkPermissions() .then(() => {}) .catch(() => {}) }) // Token handling - storeRegistry.getStore('logout').subscribe((s, old) => { + useLogoutState.subscribe((s, old) => { if (s.version === old.version) return - storeRegistry.getState('push').dispatch.deleteToken(s.version) + usePushState.getState().dispatch.deleteToken(s.version) }) let lastCount = -1 - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.badgeState === old.badgeState) return if (!s.badgeState) return const count = s.badgeState.bigTeamBadgeCount + s.badgeState.smallTeamBadgeCount @@ -196,7 +198,7 @@ export const initPushListener = () => { lastCount = count }) - storeRegistry.getState('push').dispatch.initialPermissionsCheck() + usePushState.getState().dispatch.initialPermissionsCheck() const listenNative = async () => { const RNEmitter = getNativeEmitter() @@ -210,7 +212,7 @@ export const initPushListener = () => { logger.warn('[onNotification]: normalized notification is null/undefined') return } - storeRegistry.getState('push').dispatch.handlePush(notification) + usePushState.getState().dispatch.handlePush(notification) } try { @@ -224,14 +226,14 @@ export const initPushListener = () => { const token = payload?.token if (token) { logger.debug('[PushToken] received token via onPushToken event: ', token) - storeRegistry.getState('push').dispatch.setPushToken(token) + usePushState.getState().dispatch.setPushToken(token) } }) } if (isAndroid) { - DeviceEventEmitter.addListener('onShareData', (evt: {text?: string; localPaths?: Array}) => { - const {setAndroidShare} = storeRegistry.getState('config').dispatch + RNEmitter.addListener('onShareData', (evt: {text?: string; localPaths?: Array}) => { + const {setAndroidShare} = useConfigState.getState().dispatch const text = evt.text const urls = evt.localPaths @@ -244,7 +246,7 @@ export const initPushListener = () => { return } try { - storeRegistry.getState('deeplinks').dispatch.handleAppLink('keybase://incoming-share') + handleAppLink('keybase://incoming-share') } catch {} }) } @@ -256,7 +258,7 @@ export const initPushListener = () => { try { const pushToken = await getRegistrationToken() logger.debug('[PushToken] received new token: ', pushToken) - storeRegistry.getState('push').dispatch.setPushToken(pushToken) + usePushState.getState().dispatch.setPushToken(pushToken) } catch (e) { logger.warn('[PushToken] failed to get token (will retry later): ', e) // Token will be retrieved later when permissions are checked diff --git a/shared/constants/init/shared.tsx b/shared/constants/init/shared.tsx new file mode 100644 index 000000000000..830d47dd9019 --- /dev/null +++ b/shared/constants/init/shared.tsx @@ -0,0 +1,923 @@ +import * as EngineGen from '@/actions/engine-gen-gen' +import * as T from '../types' +import isEqual from 'lodash/isEqual' +import logger from '@/logger' +import * as Tabs from '@/constants/tabs' +import type * as UseArchiveStateType from '@/stores/archive' +import type * as UseAutoResetStateType from '@/stores/autoreset' +import type * as UseBotsStateType from '@/stores/bots' +import type * as UseChatStateType from '@/stores/chat2' +import type * as UseDevicesStateType from '@/stores/devices' +import type * as UseFSStateType from '@/stores/fs' +import type * as UseGitStateType from '@/stores/git' +import type * as UseNotificationsStateType from '@/stores/notifications' +import type * as UsePeopleStateType from '@/stores/people' +import type * as UsePinentryStateType from '@/stores/pinentry' +import type * as UseSettingsPasswordStateType from '@/stores/settings-password' +import type * as UseSignupStateType from '@/stores/signup' +import type * as UseTeamsStateType from '@/stores/teams' +import type * as UseTracker2StateType from '@/stores/tracker2' +import type * as UseUnlockFoldersStateType from '@/stores/unlock-folders' +import type * as UseUsersStateType from '@/stores/users' +import {createTBStore, getTBStore} from '@/stores/team-building' +import {getSelectedConversation} from '@/constants/chat2/common' +import {handleKeybaseLink} from '@/constants/deeplinks' +import {ignorePromise} from '../utils' +import {isMobile, serverConfigFileName} from '../platform' +import {storeRegistry} from '@/stores/store-registry' +import {useAutoResetState} from '@/stores/autoreset' +import {useAvatarState} from '@/common-adapters/avatar/store' +import {useChatState} from '@/stores/chat2' +import {useConfigState} from '@/stores/config' +import {useCryptoState} from '@/stores/crypto' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' +import {useFSState} from '@/stores/fs' +import {useFollowerState} from '@/stores/followers' +import {useNotifState} from '@/stores/notifications' +import {useProfileState} from '@/stores/profile' +import {useProvisionState} from '@/stores/provision' +import {usePushState} from '@/stores/push' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsState} from '@/stores/settings' +import {useSignupState} from '@/stores/signup' +import {useState as useRecoverPasswordState} from '@/stores/recover-password' +import {useTeamsState} from '@/stores/teams' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useWhatsNewState} from '@/stores/whats-new' +import {useRouterState} from '@/stores/router2' +import * as Util from '@/constants/router2' +import {setConvoDefer} from '@/stores/convostate' + +let _emitStartupOnLoadDaemonConnectedOnce = false +let _devicesLoaded = false +let _gitLoaded = false + +export const onEngineConnected = () => { + { + const registerUIs = async () => { + try { + await T.RPCGen.delegateUiCtlRegisterChatUIRpcPromise() + await T.RPCGen.delegateUiCtlRegisterLogUIRpcPromise() + logger.info('Registered Chat UI') + await T.RPCGen.delegateUiCtlRegisterHomeUIRpcPromise() + logger.info('Registered home UI') + await T.RPCGen.delegateUiCtlRegisterSecretUIRpcPromise() + logger.info('Registered secret ui') + await T.RPCGen.delegateUiCtlRegisterIdentify3UIRpcPromise() + logger.info('Registered identify ui') + await T.RPCGen.delegateUiCtlRegisterRekeyUIRpcPromise() + logger.info('Registered rekey ui') + } catch (error) { + logger.error('Error in registering UIs:', error) + } + } + ignorePromise(registerUIs()) + } + useConfigState.getState().dispatch.onEngineConnected() + storeRegistry.getState('daemon').dispatch.startHandshake() + { + const notifyCtl = async () => { + try { + // prettier-ignore + await T.RPCGen.notifyCtlSetNotificationsRpcPromise({ + channels: { + allowChatNotifySkips: true, app: true, audit: true, badges: true, chat: true, chatarchive: true, + chatattachments: true, chatdev: false, chatemoji: false, chatemojicross: false, chatkbfsedits: false, + deviceclone: false, ephemeral: false, favorites: false, featuredBots: true, kbfs: true, kbfsdesktop: !isMobile, + kbfslegacy: false, kbfsrequest: false, kbfssubscription: true, keyfamily: false, notifysimplefs: true, + paperkeys: false, pgp: true, reachability: true, runtimestats: true, saltpack: true, service: true, session: true, + team: true, teambot: false, tracking: true, users: true, wallet: false, + }, + }) + } catch (error) { + if (error) { + logger.warn('error in toggling notifications: ', error) + } + } + } + ignorePromise(notifyCtl()) + } +} + +export const onEngineDisconnected = () => { + const f = async () => { + await logger.dump() + } + ignorePromise(f()) + storeRegistry.getState('daemon').dispatch.setError(new Error('Disconnected')) +} + +// Initialize team building callbacks. Not ideal but keeping all the existing logic for now. +export const initTeamBuildingCallbacks = () => { + const commonCallbacks = { + onAddMembersWizardPushMembers: (members: Array) => { + useTeamsState.getState().dispatch.addMembersWizardPushMembers(members) + }, + onGetSettingsContactsImportEnabled: () => { + return useSettingsContactsState.getState().importEnabled + }, + onGetSettingsContactsUserCountryCode: () => { + return useSettingsContactsState.getState().userCountryCode + }, + onShowUserProfile: (username: string) => { + useProfileState.getState().dispatch.showUserProfile(username) + }, + onUsersGetBlockState: (usernames: ReadonlyArray) => { + useUsersState.getState().dispatch.getBlockState(usernames) + }, + onUsersUpdates: (infos: ReadonlyArray<{name: string; info: Partial}>) => { + useUsersState.getState().dispatch.updates(infos) + }, + } + + const namespaces: Array = ['chat2', 'crypto', 'teams', 'people'] + for (const namespace of namespaces) { + const store = createTBStore(namespace) + const currentState = store.getState() + store.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + ...commonCallbacks, + ...(namespace === 'chat2' + ? { + onFinishedTeamBuildingChat: users => { + storeRegistry.getState('chat').dispatch.onTeamBuildingFinished(users) + }, + } + : {}), + ...(namespace === 'crypto' + ? { + onFinishedTeamBuildingCrypto: users => { + useCryptoState.getState().dispatch.onTeamBuildingFinished(users) + }, + } + : {}), + }, + }, + }) + } +} + +export const initAutoResetCallbacks = () => { + const currentState = useAutoResetState.getState() + useAutoResetState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + onGetRecoverPasswordUsername: () => { + return storeRegistry.getState('recover-password').username + }, + onStartProvision: (username: string, fromReset: boolean) => { + storeRegistry.getState('provision').dispatch.startProvision(username, fromReset) + }, + }, + }, + }) +} + +export const initChat2Callbacks = () => { + const currentState = useChatState.getState() + useChatState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + onGetDaemonState: () => { + const daemonState = storeRegistry.getState('daemon') + return {dispatch: daemonState.dispatch, handshakeVersion: daemonState.handshakeVersion} + }, + onGetTeamsTeamIDToMembers: (teamID: T.Teams.TeamID) => { + return storeRegistry.getState('teams').teamIDToMembers.get(teamID) + }, + onGetUsersInfoMap: () => { + return storeRegistry.getState('users').infoMap + }, + onTeamsGetMembers: (teamID: T.Teams.TeamID) => { + storeRegistry.getState('teams').dispatch.getMembers(teamID) + }, + onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => { + storeRegistry.getState('teams').dispatch.updateTeamRetentionPolicy(metas) + }, + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => { + storeRegistry.getState('users').dispatch.updates(updates) + }, + }, + }, + }) +} + +export const initTeamsCallbacks = () => { + const currentState = useTeamsState.getState() + useTeamsState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onChatNavigateToInbox: (allowSwitchTab?: boolean) => { + storeRegistry.getState('chat').dispatch.navigateToInbox(allowSwitchTab) + }, + onChatPreviewConversation: ( + p: Parameters['dispatch']['previewConversation']>[0] + ) => { + storeRegistry.getState('chat').dispatch.previewConversation(p) + }, + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => { + storeRegistry.getState('users').dispatch.updates(updates) + }, + }, + }, + }) +} + +export const initFSCallbacks = () => { + const currentState = useFSState.getState() + useFSState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onBadgeApp: (key: 'kbfsUploading' | 'outOfSpace', on: boolean) => { + useNotifState.getState().dispatch.badgeApp(key, on) + }, + onSetBadgeCounts: (counts: Map) => { + useNotifState.getState().dispatch.setBadgeCounts(counts) + }, + }, + }, + }) +} + +export const initNotificationsCallbacks = () => { + const currentState = useNotifState.getState() + useNotifState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onFavoritesLoad: () => { + useFSState.getState().dispatch.favoritesLoad() + }, + }, + }, + }) +} + +export const initProfileCallbacks = () => { + const currentState = useProfileState.getState() + useProfileState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onTracker2GetDetails: (username: string) => { + return useTrackerState.getState().getDetails(username) + }, + onTracker2Load: ( + params: Parameters['dispatch']['load']>[0] + ) => { + useTrackerState.getState().dispatch.load(params) + }, + onTracker2ShowUser: (username: string, asTracker: boolean, skipNav?: boolean) => { + useTrackerState.getState().dispatch.showUser(username, asTracker, skipNav) + }, + onTracker2UpdateResult: (guiID: string, result: T.Tracker.DetailsState, reason?: string) => { + useTrackerState.getState().dispatch.updateResult(guiID, result, reason) + }, + }, + }, + }) +} + +export const initPushCallbacks = () => { + const currentState = usePushState.getState() + usePushState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onGetDaemonHandshakeState: () => { + return useDaemonState.getState().handshakeState + }, + onNavigateToThread: ( + conversationIDKey: T.Chat.ConversationIDKey, + reason: 'push' | 'extension', + pushBody?: string + ) => { + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.navigateToThread(reason, undefined, pushBody) + }, + onShowUserProfile: (username: string) => { + useProfileState.getState().dispatch.showUserProfile(username) + }, + }, + }, + }) +} + +export const initRecoverPasswordCallbacks = () => { + const currentState = useRecoverPasswordState.getState() + useRecoverPasswordState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onProvisionCancel: (ignoreWarning?: boolean) => { + useProvisionState.getState().dispatch.dynamic.cancel?.(ignoreWarning) + }, + onStartAccountReset: (skipPassword: boolean, username: string) => { + useAutoResetState.getState().dispatch.startAccountReset(skipPassword, username) + }, + }, + }, + }) +} + +export const initSignupCallbacks = () => { + const currentState = useSignupState.getState() + useSignupState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onEditEmail: (p: {email: string; makeSearchable: boolean}) => { + useSettingsEmailState.getState().dispatch.editEmail(p) + }, + onShowPermissionsPrompt: (p: {justSignedUp?: boolean}) => { + usePushState.getState().dispatch.showPermissionsPrompt(p) + }, + }, + }, + }) +} + +export const initTracker2Callbacks = () => { + const currentState = useTrackerState.getState() + useTrackerState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onShowUserProfile: (username: string) => { + useProfileState.getState().dispatch.showUserProfile(username) + }, + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => { + useUsersState.getState().dispatch.updates(updates) + }, + }, + }, + }) +} + +export const initSettingsCallbacks = () => { + const currentState = useSettingsState.getState() + useSettingsState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + getSettingsPhonePhones: () => { + return useSettingsPhoneState.getState().phones + }, + onSettingsEmailNotifyEmailsChanged: (emails: ReadonlyArray) => { + useSettingsEmailState.getState().dispatch.notifyEmailAddressEmailsChanged(emails) + }, + onSettingsPhoneSetNumbers: (phoneNumbers?: ReadonlyArray) => { + useSettingsPhoneState.getState().dispatch.setNumbers(phoneNumbers) + }, + }, + }, + }) +} + +export const initSharedSubscriptions = () => { + setConvoDefer({ + chatBlockButtonsMapHas: teamID => + storeRegistry.getState('chat').blockButtonsMap.has(teamID), + chatInboxLayoutSmallTeamsFirstConvID: () => + storeRegistry.getState('chat').inboxLayout?.smallTeams?.[0]?.convID, + chatInboxRefresh: reason => + storeRegistry.getState('chat').dispatch.inboxRefresh(reason), + chatMetasReceived: metas => + storeRegistry.getState('chat').dispatch.metasReceived(metas), + chatNavigateToInbox: () => + storeRegistry.getState('chat').dispatch.navigateToInbox(), + chatPaymentInfoReceived: (_messageID, paymentInfo) => + storeRegistry.getState('chat').dispatch.paymentInfoReceived(paymentInfo), + chatPreviewConversation: p => + storeRegistry.getState('chat').dispatch.previewConversation(p), + chatResetConversationErrored: () => + storeRegistry.getState('chat').dispatch.resetConversationErrored(), + chatUnboxRows: (convIDs, force) => + storeRegistry.getState('chat').dispatch.unboxRows(convIDs, force), + chatUpdateInfoPanel: (show, tab) => + storeRegistry.getState('chat').dispatch.updateInfoPanel(show, tab), + teamsGetMembers: teamID => + storeRegistry.getState('teams').dispatch.getMembers(teamID), + usersGetBio: username => + storeRegistry.getState('users').dispatch.getBio(username), + }) + useConfigState.subscribe((s, old) => { + if (s.loadOnStartPhase !== old.loadOnStartPhase) { + if (s.loadOnStartPhase === 'startupOrReloginButNotInARush') { + const getFollowerInfo = () => { + const {uid} = useCurrentUserState.getState() + logger.info(`getFollowerInfo: init; uid=${uid}`) + if (uid) { + // request follower info in the background + T.RPCGen.configRequestFollowingAndUnverifiedFollowersRpcPromise() + .then(() => {}) + .catch(() => {}) + } + } + + const updateServerConfig = async () => { + if (s.loggedIn) { + try { + await T.RPCGen.configUpdateLastLoggedInAndServerConfigRpcPromise({ + serverConfigPath: serverConfigFileName, + }) + } catch {} + } + } + + const updateTeams = () => { + useTeamsState.getState().dispatch.getTeams() + useTeamsState.getState().dispatch.refreshTeamRoleMap() + } + + const updateSettings = () => { + useSettingsContactsState.getState().dispatch.loadContactImportEnabled() + } + + const updateChat = async () => { + // On login lets load the untrusted inbox. This helps make some flows easier + if (useCurrentUserState.getState().username) { + const {inboxRefresh} = useChatState.getState().dispatch + inboxRefresh('bootstrap') + } + try { + const rows = await T.RPCGen.configGuiGetValueRpcPromise({path: 'ui.inboxSmallRows'}) + const ri = rows.i ?? -1 + if (ri > 0) { + useChatState.getState().dispatch.setInboxNumSmallRows(ri, true) + } + } catch {} + } + + getFollowerInfo() + ignorePromise(updateServerConfig()) + updateTeams() + updateSettings() + ignorePromise(updateChat()) + } + } + + if (s.gregorReachable !== old.gregorReachable) { + // Re-get info about our account if you log in/we're done handshaking/became reachable + if (s.gregorReachable === T.RPCGen.Reachable.yes) { + // not in waiting state + if (storeRegistry.getState('daemon').handshakeWaiters.size === 0) { + ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) + } + storeRegistry.getState('teams').dispatch.eagerLoadTeams() + } + } + + if (s.installerRanCount !== old.installerRanCount) { + storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() + } + + if (s.loggedIn !== old.loggedIn) { + if (s.loggedIn) { + ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) + storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() + } + storeRegistry + .getState('daemon') + .dispatch.loadDaemonAccounts( + s.configuredAccounts.length, + s.loggedIn, + useConfigState.getState().dispatch.refreshAccounts + ) + if (!s.loggedInCausedbyStartup) { + ignorePromise(useConfigState.getState().dispatch.refreshAccounts()) + } + } + + if (s.mobileAppState !== old.mobileAppState) { + if (s.mobileAppState === 'background' && storeRegistry.getState('chat').inboxSearch) { + storeRegistry.getState('chat').dispatch.toggleInboxSearch(false) + } + } + + if (s.revokedTrigger !== old.revokedTrigger) { + storeRegistry + .getState('daemon') + .dispatch.loadDaemonAccounts( + s.configuredAccounts.length, + s.loggedIn, + useConfigState.getState().dispatch.refreshAccounts + ) + } + + if (s.configuredAccounts !== old.configuredAccounts) { + const updates = s.configuredAccounts.map(account => ({ + info: {fullname: account.fullname ?? ''}, + name: account.username, + })) + if (updates.length > 0) { + storeRegistry.getState('users').dispatch.updates(updates) + } + } + + if (s.gregorPushState !== old.gregorPushState) { + const lastSeenItem = s.gregorPushState.find(i => i.item.category === 'whatsNewLastSeenVersion') + useWhatsNewState.getState().dispatch.updateLastSeen(lastSeenItem) + } + + if (s.active !== old.active) { + const cs = storeRegistry.getConvoState(getSelectedConversation()) + cs.dispatch.markThreadAsRead() + } + }) + + useDaemonState.subscribe((s, old) => { + if (s.handshakeVersion !== old.handshakeVersion) { + useDarkModeState.getState().dispatch.loadDarkPrefs() + storeRegistry.getState('chat').dispatch.loadStaticConfig() + const configState = useConfigState.getState() + s.dispatch.loadDaemonAccounts( + configState.configuredAccounts.length, + configState.loggedIn, + useConfigState.getState().dispatch.refreshAccounts + ) + } + + if (s.bootstrapStatus !== old.bootstrapStatus) { + const bootstrap = s.bootstrapStatus + if (bootstrap) { + const {deviceID, deviceName, loggedIn, uid, username, userReacjis} = bootstrap + useCurrentUserState.getState().dispatch.setBootstrap({deviceID, deviceName, uid, username}) + + const configDispatch = useConfigState.getState().dispatch + if (username) { + configDispatch.setDefaultUsername(username) + } + if (loggedIn) { + configDispatch.setUserSwitching(false) + } + configDispatch.setLoggedIn(loggedIn, false) + + if (bootstrap.httpSrvInfo) { + configDispatch.setHTTPSrvInfo(bootstrap.httpSrvInfo.address, bootstrap.httpSrvInfo.token) + } + + storeRegistry.getState('chat').dispatch.updateUserReacjis(userReacjis) + } + } + + if (s.handshakeState !== old.handshakeState) { + if (s.handshakeState === 'done') { + if (!_emitStartupOnLoadDaemonConnectedOnce) { + _emitStartupOnLoadDaemonConnectedOnce = true + useConfigState.getState().dispatch.loadOnStart('connectedToDaemonForFirstTime') + } + } + } + }) + + useProvisionState.subscribe((s, old) => { + if (s.startProvisionTrigger !== old.startProvisionTrigger) { + useConfigState.getState().dispatch.setLoginError() + useConfigState.getState().dispatch.resetRevokedSelf() + const f = async () => { + // If we're logged in, we're coming from the user switcher; log out first to prevent the service from getting out of sync with the GUI about our logged-in-ness + if (useConfigState.getState().loggedIn) { + await T.RPCGen.loginLogoutRpcPromise({force: false, keepSecrets: true}, 'config:loginAsOther') + } + } + ignorePromise(f()) + } + }) + + useRouterState.subscribe((s, old) => { + const next = s.navState as Util.NavState + const prev = old.navState as Util.NavState + if (prev === next) return + + const namespaces = ['chat2', 'crypto', 'teams', 'people'] as const + const namespaceToRoute = new Map([ + ['chat2', 'chatNewChat'], + ['crypto', 'cryptoTeamBuilder'], + ['teams', 'teamsTeamBuilder'], + ['people', 'peopleTeamBuilder'], + ]) + for (const namespace of namespaces) { + const wasTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(prev)?.name + if (wasTeamBuilding) { + // team building or modal on top of that still + const isTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(next)?.name + if (!isTeamBuilding) { + getTBStore(namespace).dispatch.cancelTeamBuilding() + } + } + } + + // Clear critical update when we nav away from tab + if ( + prev && + Util.getTab(prev) === Tabs.fsTab && + next && + Util.getTab(next) !== Tabs.fsTab && + storeRegistry.getState('fs').criticalUpdate + ) { + const {dispatch} = storeRegistry.getState('fs') + dispatch.setCriticalUpdate(false) + } + const fsRrouteNames = ['fsRoot', 'barePreview'] + const wasScreen = fsRrouteNames.includes(Util.getVisibleScreen(prev)?.name ?? '') + const isScreen = fsRrouteNames.includes(Util.getVisibleScreen(next)?.name ?? '') + if (wasScreen !== isScreen) { + const {dispatch} = storeRegistry.getState('fs') + if (wasScreen) { + dispatch.userOut() + } else { + dispatch.userIn() + } + } + + // Clear "just signed up email" when you leave the people tab after signup + if ( + prev && + Util.getTab(prev) === Tabs.peopleTab && + next && + Util.getTab(next) !== Tabs.peopleTab && + storeRegistry.getState('signup').justSignedUpEmail + ) { + storeRegistry.getState('signup').dispatch.clearJustSignedUpEmail() + } + + if (prev && Util.getTab(prev) === Tabs.peopleTab && next && Util.getTab(next) !== Tabs.peopleTab) { + storeRegistry.getState('people').dispatch.markViewed() + } + + if (prev && Util.getTab(prev) === Tabs.teamsTab && next && Util.getTab(next) !== Tabs.teamsTab) { + storeRegistry.getState('teams').dispatch.clearNavBadges() + } + + // Clear "check your inbox" in settings when you leave the settings tab + if ( + prev && + Util.getTab(prev) === Tabs.settingsTab && + next && + Util.getTab(next) !== Tabs.settingsTab && + storeRegistry.getState('settings-email').addedEmail + ) { + storeRegistry.getState('settings-email').dispatch.resetAddedEmail() + } + + storeRegistry.getState('chat').dispatch.onRouteChanged(prev, next) + }) + + initAutoResetCallbacks() + initChat2Callbacks() + initTeamBuildingCallbacks() + initTeamsCallbacks() + initFSCallbacks() + initNotificationsCallbacks() + initProfileCallbacks() + initPushCallbacks() + initRecoverPasswordCallbacks() + initSettingsCallbacks() + initSignupCallbacks() + initTracker2Callbacks() +} + +// This is to defer loading stores we don't need immediately. +export const _onEngineIncoming = (action: EngineGen.Actions) => { + switch (action.type) { + case EngineGen.keybase1NotifySimpleFSSimpleFSArchiveStatusChanged: + case EngineGen.chat1NotifyChatChatArchiveComplete: + case EngineGen.chat1NotifyChatChatArchiveProgress: + { + const {useArchiveState} = require('@/stores/archive') as typeof UseArchiveStateType + useArchiveState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyBadgesBadgeState: + { + const {useAutoResetState} = require('@/stores/autoreset') as typeof UseAutoResetStateType + useAutoResetState.getState().dispatch.onEngineIncomingImpl(action) + + const {badgeState} = action.payload.params + const {newDevices, revokedDevices} = badgeState + const hasValue = (newDevices?.length ?? 0) + (revokedDevices?.length ?? 0) > 0 + if (_devicesLoaded || hasValue) { + _devicesLoaded = true + const {useDevicesState} = require('@/stores/devices') as typeof UseDevicesStateType + useDevicesState.getState().dispatch.onEngineIncomingImpl(action) + } + + const badges = new Set(badgeState.newGitRepoGlobalUniqueIDs) + if (_gitLoaded || badges.size) { + _gitLoaded = true + const {useGitState} = require('@/stores/git') as typeof UseGitStateType + useGitState.getState().dispatch.onEngineIncomingImpl(action) + } + + const {useNotifState} = require('@/stores/notifications') as typeof UseNotificationsStateType + useNotifState.getState().dispatch.onEngineIncomingImpl(action) + + const {useTeamsState} = require('@/stores/teams') as typeof UseTeamsStateType + useTeamsState.getState().dispatch.onEngineIncomingImpl(action) + + const {useChatState} = require('@/stores/chat2') as typeof UseChatStateType + useChatState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.chat1ChatUiChatShowManageChannels: + case EngineGen.keybase1NotifyTeamTeamMetadataUpdate: + case EngineGen.chat1NotifyChatChatWelcomeMessageLoaded: + case EngineGen.keybase1NotifyTeamTeamTreeMembershipsPartial: + case EngineGen.keybase1NotifyTeamTeamTreeMembershipsDone: + case EngineGen.keybase1NotifyTeamTeamRoleMapChanged: + case EngineGen.keybase1NotifyTeamTeamChangedByID: + case EngineGen.keybase1NotifyTeamTeamDeleted: + case EngineGen.keybase1NotifyTeamTeamExit: + case EngineGen.keybase1GregorUIPushState: + { + const {useTeamsState} = require('@/stores/teams') as typeof UseTeamsStateType + useTeamsState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyFeaturedBotsFeaturedBotsUpdate: + { + const {useBotsState} = require('@/stores/bots') as typeof UseBotsStateType + useBotsState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyFSFSOverallSyncStatusChanged: + case EngineGen.keybase1NotifyFSFSSubscriptionNotifyPath: + case EngineGen.keybase1NotifyFSFSSubscriptionNotify: + { + const {useFSState} = require('@/stores/fs') as typeof UseFSStateType + useFSState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyAuditRootAuditError: + case EngineGen.keybase1NotifyAuditBoxAuditError: + { + const {useNotifState} = require('@/stores/notifications') as typeof UseNotificationsStateType + useNotifState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1HomeUIHomeUIRefresh: + case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: + { + const {usePeopleState} = require('@/stores/people') as typeof UsePeopleStateType + usePeopleState.getState().dispatch.onEngineIncomingImpl(action) + const emailAddress = action.payload.params?.emailAddress + if (emailAddress) { + storeRegistry.getState('settings-email').dispatch.notifyEmailVerified(emailAddress) + } + const {useSignupState} = require('@/stores/signup') as typeof UseSignupStateType + useSignupState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1SecretUiGetPassphrase: + { + const {usePinentryState} = require('@/stores/pinentry') as typeof UsePinentryStateType + usePinentryState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyUsersPasswordChanged: + { + const randomPW = action.payload.params.state === T.RPCGen.PassphraseState.random + const {usePWState} = require('@/stores/settings-password') as typeof UseSettingsPasswordStateType + usePWState.getState().dispatch.notifyUsersPasswordChanged(randomPW) + } + break + case EngineGen.keybase1NotifyPhoneNumberPhoneNumbersChanged: { + const {list} = action.payload.params + storeRegistry + .getState('settings-phone') + .dispatch.notifyPhoneNumberPhoneNumbersChanged(list ?? undefined) + break + } + case EngineGen.keybase1NotifyEmailAddressEmailsChanged: { + const list = action.payload.params.list ?? [] + storeRegistry.getState('settings-email').dispatch.notifyEmailAddressEmailsChanged(list) + break + } + case EngineGen.chat1ChatUiChatInboxFailed: + case EngineGen.chat1NotifyChatChatSetConvSettings: + case EngineGen.chat1NotifyChatChatAttachmentUploadStart: + case EngineGen.chat1NotifyChatChatPromptUnfurl: + case EngineGen.chat1NotifyChatChatPaymentInfo: + case EngineGen.chat1NotifyChatChatRequestInfo: + case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: + case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: + case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: + case EngineGen.chat1ChatUiChatCommandMarkdown: + case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: + case EngineGen.chat1ChatUiChatCommandStatus: + case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: + case EngineGen.chat1ChatUiChatGiphySearchResults: + case EngineGen.chat1NotifyChatChatParticipantsInfo: + case EngineGen.chat1ChatUiChatMaybeMentionUpdate: + case EngineGen.chat1NotifyChatChatConvUpdate: + case EngineGen.chat1ChatUiChatCoinFlipStatus: + case EngineGen.chat1NotifyChatChatThreadsStale: + case EngineGen.chat1NotifyChatChatSubteamRename: + case EngineGen.chat1NotifyChatChatTLFFinalize: + case EngineGen.chat1NotifyChatChatIdentifyUpdate: + case EngineGen.chat1ChatUiChatInboxUnverified: + case EngineGen.chat1NotifyChatChatInboxSyncStarted: + case EngineGen.chat1NotifyChatChatInboxSynced: + case EngineGen.chat1ChatUiChatInboxLayout: + case EngineGen.chat1NotifyChatChatInboxStale: + case EngineGen.chat1ChatUiChatInboxConversation: + case EngineGen.chat1NotifyChatNewChatActivity: + case EngineGen.chat1NotifyChatChatTypingUpdate: + case EngineGen.chat1NotifyChatChatSetConvRetention: + case EngineGen.chat1NotifyChatChatSetTeamRetention: + { + const {useChatState} = require('@/stores/chat2') as typeof UseChatStateType + useChatState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyServiceHandleKeybaseLink: + { + const {link, deferred} = action.payload.params + if (deferred && !link.startsWith('keybase://team-invite-link/')) { + return + } + handleKeybaseLink(link) + } + break + case EngineGen.keybase1NotifyTeamAvatarUpdated: { + const {name} = action.payload.params + useAvatarState.getState().dispatch.updated(name) + break + } + case EngineGen.keybase1NotifyTrackingTrackingChanged: { + const {isTracking, username} = action.payload.params + useFollowerState.getState().dispatch.updateFollowing(username, isTracking) + const {useTrackerState} = require('@/stores/tracker2') as typeof UseTracker2StateType + useTrackerState.getState().dispatch.onEngineIncomingImpl(action) + break + } + case EngineGen.keybase1NotifyTrackingTrackingInfo: { + const {uid, followers: _newFollowers, followees: _newFollowing} = action.payload.params + if (useCurrentUserState.getState().uid !== uid) { + break + } + const newFollowers = new Set(_newFollowers) + const newFollowing = new Set(_newFollowing) + const {following: oldFollowing, followers: oldFollowers, dispatch} = useFollowerState.getState() + const following = isEqual(newFollowing, oldFollowing) ? oldFollowing : newFollowing + const followers = isEqual(newFollowers, oldFollowers) ? oldFollowers : newFollowers + dispatch.replace(followers, following) + break + } + case EngineGen.keybase1Identify3UiIdentify3Result: + case EngineGen.keybase1Identify3UiIdentify3ShowTracker: + case EngineGen.keybase1NotifyUsersUserChanged: + case EngineGen.keybase1NotifyTrackingNotifyUserBlocked: + case EngineGen.keybase1Identify3UiIdentify3UpdateRow: + case EngineGen.keybase1Identify3UiIdentify3UserReset: + case EngineGen.keybase1Identify3UiIdentify3UpdateUserCard: + case EngineGen.keybase1Identify3UiIdentify3Summary: + { + const {useTrackerState} = require('@/stores/tracker2') as typeof UseTracker2StateType + useTrackerState.getState().dispatch.onEngineIncomingImpl(action) + } + { + const {useUsersState} = require('@/stores/users') as typeof UseUsersStateType + useUsersState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyUsersIdentifyUpdate: + { + const {useUsersState} = require('@/stores/users') as typeof UseUsersStateType + useUsersState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1RekeyUIRefresh: + case EngineGen.keybase1RekeyUIDelegateRekeyUI: + { + const {useUnlockFoldersState} = require('@/stores/unlock-folders') as typeof UseUnlockFoldersStateType + useUnlockFoldersState.getState().dispatch.onEngineIncomingImpl(action) + } + break + default: + } + useConfigState.getState().dispatch.onEngineIncoming(action) +} diff --git a/shared/constants/notifications/util.tsx b/shared/constants/notifications/util.tsx deleted file mode 100644 index acc95a39dbf0..000000000000 --- a/shared/constants/notifications/util.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import {isMobile} from '../platform' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.notifyCtlSetNotificationsRpcPromise({ - channels: { - allowChatNotifySkips: true, - app: true, - audit: true, - badges: true, - chat: true, - chatarchive: true, - chatattachments: true, - chatdev: false, - chatemoji: false, - chatemojicross: false, - chatkbfsedits: false, - deviceclone: false, - ephemeral: false, - favorites: false, - featuredBots: true, - kbfs: true, - kbfsdesktop: !isMobile, - kbfslegacy: false, - kbfsrequest: false, - kbfssubscription: true, - keyfamily: false, - notifysimplefs: true, - paperkeys: false, - pgp: true, - reachability: true, - runtimestats: true, - saltpack: true, - service: true, - session: true, - team: true, - teambot: false, - tracking: true, - users: true, - wallet: false, - }, - }) - } catch (error) { - if (error) { - logger.warn('error in toggling notifications: ', error) - } - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyAuditRootAuditError: - case EngineGen.keybase1NotifyAuditBoxAuditError: - case EngineGen.keybase1NotifyBadgesBadgeState: - { - storeRegistry.getState('notifications').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/people/util.tsx b/shared/constants/people/util.tsx deleted file mode 100644 index 8deb5593c959..000000000000 --- a/shared/constants/people/util.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterHomeUIRpcPromise() - console.log('Registered home UI') - } catch (error) { - console.warn('Error in registering home UI:', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1HomeUIHomeUIRefresh: - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - { - storeRegistry.getState('people').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/pinentry/util.tsx b/shared/constants/pinentry/util.tsx deleted file mode 100644 index 5ec547f0d1cf..000000000000 --- a/shared/constants/pinentry/util.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterSecretUIRpcPromise() - logger.info('Registered secret ui') - } catch (error) { - logger.warn('error in registering secret ui: ', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1SecretUiGetPassphrase: - { - storeRegistry.getState('pinentry').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/platform-specific/index.desktop.tsx b/shared/constants/platform-specific/index.desktop.tsx deleted file mode 100644 index b9f8f70f7ade..000000000000 --- a/shared/constants/platform-specific/index.desktop.tsx +++ /dev/null @@ -1,290 +0,0 @@ -import * as Chat from '../chat2' -import {ignorePromise} from '../utils' -import {storeRegistry} from '../store-registry' -import * as ConfigConstants from '../config' -import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' -import InputMonitor from './input-monitor.desktop' -import KB2 from '@/util/electron.desktop' -import logger from '@/logger' -import type {RPCError} from '@/util/errors' -import {getEngine} from '@/engine' -import {isLinux, isWindows} from '../platform.desktop' -import {kbfsNotification} from './kbfs-notifications' -import {skipAppFocusActions} from '@/local-debug.desktop' -import NotifyPopup from '@/util/notify-popup' -import {noKBFSFailReason} from '@/constants/config/util' -import {wrapErrors} from '@/util/debug' - -const {showMainWindow, activeChanged, requestWindowsStartService, dumpNodeLogger} = KB2.functions -const {quitApp, exitApp, setOpenAtLogin, ctlQuit, copyToClipboard} = KB2.functions - -export const requestPermissionsToWrite = async () => { - return Promise.resolve(true) -} - -export function showShareActionSheet() { - throw new Error('Show Share Action - unsupported on this platform') -} -export async function saveAttachmentToCameraRoll() { - return Promise.reject(new Error('Save Attachment to camera roll - unsupported on this platform')) -} - -export const requestLocationPermission = async () => Promise.resolve() -export const watchPositionForMap = async () => Promise.resolve(() => {}) - -const maybePauseVideos = () => { - const {appFocused} = storeRegistry.getState('config') - const videos = document.querySelectorAll('video') - const allVideos = Array.from(videos) - - allVideos.forEach(v => { - if (appFocused) { - if (v.hasAttribute('data-focus-paused')) { - if (v.paused) { - v.play() - .then(() => {}) - .catch(() => {}) - } - } - } else { - // only pause looping videos - if (!v.paused && v.hasAttribute('loop') && v.hasAttribute('autoplay')) { - v.setAttribute('data-focus-paused', 'true') - v.pause() - } - } - }) -} - -export const dumpLogs = async (reason?: string) => { - await logger.dump() - await (dumpNodeLogger?.() ?? Promise.resolve([])) - // quit as soon as possible - if (reason === 'quitting through menu') { - ctlQuit?.() - } -} - -export const initPlatformListener = () => { - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.dumpLogsNative = dumpLogs - s.dispatch.dynamic.showMainNative = wrapErrors(() => showMainWindow?.()) - s.dispatch.dynamic.copyToClipboard = wrapErrors((s: string) => copyToClipboard?.(s)) - s.dispatch.dynamic.onEngineConnectedDesktop = wrapErrors(() => { - // Introduce ourselves to the service - const f = async () => { - await T.RPCGen.configHelloIAmRpcPromise({details: KB2.constants.helloDetails}) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.onEngineIncomingDesktop = wrapErrors((action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1LogsendPrepareLogsend: { - const f = async () => { - const response = action.payload.response - try { - await dumpLogs() - } finally { - response.result() - } - } - ignorePromise(f()) - break - } - case EngineGen.keybase1NotifyAppExit: - console.log('App exit requested') - exitApp?.(0) - break - case EngineGen.keybase1NotifyFSFSActivity: - kbfsNotification(action.payload.params.notification, NotifyPopup) - break - case EngineGen.keybase1NotifyPGPPgpKeyInSecretStoreFile: { - const f = async () => { - try { - await T.RPCGen.pgpPgpStorageDismissRpcPromise() - } catch (err) { - console.warn('Error in sending pgpPgpStorageDismissRpc:', err) - } - } - ignorePromise(f()) - break - } - case EngineGen.keybase1NotifyServiceShutdown: { - const {code} = action.payload.params - if (isWindows && code !== (T.RPCGen.ExitCode.restart as number)) { - console.log('Quitting due to service shutdown with code: ', code) - // Quit just the app, not the service - quitApp?.() - } - break - } - - case EngineGen.keybase1LogUiLog: { - const {params} = action.payload - const {level, text} = params - logger.info('keybase.1.logUi.log:', params.text.data) - if (level >= T.RPCGen.LogLevel.error) { - NotifyPopup(text.data) - } - break - } - - case EngineGen.keybase1NotifySessionClientOutOfDate: { - const {upgradeTo, upgradeURI, upgradeMsg} = action.payload.params - const body = upgradeMsg || `Please update to ${upgradeTo} by going to ${upgradeURI}` - NotifyPopup('Client out of date!', {body}, 60 * 60) - // This is from the API server. Consider notifications from server always critical. - storeRegistry - .getState('config') - .dispatch.setOutOfDate({critical: true, message: upgradeMsg, outOfDate: true, updating: false}) - break - } - default: - } - }) - }) - - storeRegistry.getStore('config').subscribe((s, old) => { - if (s.loggedIn === old.loggedIn) return - storeRegistry.getState('config').dispatch.osNetworkStatusChanged(navigator.onLine, 'notavailable', true) - }) - - storeRegistry.getStore('config').subscribe((s, prev) => { - if (s.appFocused !== prev.appFocused) { - maybePauseVideos() - } - }) - - storeRegistry.getStore('daemon').subscribe((s, old) => { - if (s.handshakeVersion === old.handshakeVersion) return - if (!isWindows) return - - const f = async () => { - const waitKey = 'pipeCheckFail' - const version = s.handshakeVersion - const {wait} = storeRegistry.getState('daemon').dispatch - wait(waitKey, version, true) - try { - logger.info('Checking RPC ownership') - if (KB2.functions.winCheckRPCOwnership) { - await KB2.functions.winCheckRPCOwnership() - } - wait(waitKey, version, false) - } catch (error_) { - // error will be logged in bootstrap check - getEngine().reset() - const error = error_ as RPCError - wait(waitKey, version, false, error.message || 'windows pipe owner fail', true) - } - } - ignorePromise(f()) - }) - - const handleWindowFocusEvents = () => { - const handle = (appFocused: boolean) => { - if (skipAppFocusActions) { - console.log('Skipping app focus actions!') - } else { - storeRegistry.getState('config').dispatch.changedFocus(appFocused) - } - } - window.addEventListener('focus', () => handle(true)) - window.addEventListener('blur', () => handle(false)) - } - handleWindowFocusEvents() - - const setupReachabilityWatcher = () => { - window.addEventListener('online', () => - storeRegistry.getState('config').dispatch.osNetworkStatusChanged(true, 'notavailable') - ) - window.addEventListener('offline', () => - storeRegistry.getState('config').dispatch.osNetworkStatusChanged(false, 'notavailable') - ) - } - setupReachabilityWatcher() - - storeRegistry.getStore('config').subscribe((s, old) => { - if (s.openAtLogin === old.openAtLogin) return - const {openAtLogin} = s - const f = async () => { - if (__DEV__) { - console.log('onSetOpenAtLogin disabled for dev mode') - return - } else { - await T.RPCGen.configGuiSetValueRpcPromise({ - path: ConfigConstants.openAtLoginKey, - value: {b: openAtLogin, isNull: false}, - }) - } - if (isLinux || isWindows) { - const enabled = - (await T.RPCGen.ctlGetOnLoginStartupRpcPromise()) === T.RPCGen.OnLoginStartupStatus.enabled - if (enabled !== openAtLogin) { - try { - await T.RPCGen.ctlSetOnLoginStartupRpcPromise({enabled: openAtLogin}) - } catch (error_) { - const error = error_ as RPCError - logger.warn(`Error in sending ctlSetOnLoginStartup: ${error.message}`) - } - } - } else { - logger.info(`Login item settings changed! now ${openAtLogin ? 'on' : 'off'}`) - await setOpenAtLogin?.(openAtLogin) - } - } - ignorePromise(f()) - }) - - storeRegistry.getStore('daemon').subscribe((s, old) => { - if (s.handshakeState === old.handshakeState || s.handshakeState !== 'done') return - storeRegistry.getState('config').dispatch.setStartupDetails({ - conversation: Chat.noConversationIDKey, - followUser: '', - link: '', - tab: undefined, - }) - }) - - if (isLinux) { - storeRegistry.getState('config').dispatch.initUseNativeFrame() - } - storeRegistry.getState('config').dispatch.initNotifySound() - storeRegistry.getState('config').dispatch.initForceSmallNav() - storeRegistry.getState('config').dispatch.initOpenAtLogin() - storeRegistry.getState('config').dispatch.initAppUpdateLoop() - - storeRegistry.getStore('profile').setState(s => { - s.dispatch.editAvatar = () => { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {image: undefined}, selected: 'profileEditAvatar'}) - } - }) - - const initializeInputMonitor = () => { - const inputMonitor = new InputMonitor() - inputMonitor.notifyActive = (userActive: boolean) => { - if (skipAppFocusActions) { - console.log('Skipping app focus actions!') - } else { - storeRegistry.getState('active').dispatch.setActive(userActive) - // let node thread save file - activeChanged?.(Date.now(), userActive) - } - } - } - initializeInputMonitor() - - storeRegistry.getStore('daemon').setState(s => { - s.dispatch.onRestartHandshakeNative = () => { - const {handshakeFailedReason} = storeRegistry.getState('daemon') - if (isWindows && handshakeFailedReason === noKBFSFailReason) { - requestWindowsStartService?.() - } - } - }) - - ignorePromise(storeRegistry.getState('fs').dispatch.setupSubscriptions()) -} diff --git a/shared/constants/recover-password/utils.tsx b/shared/constants/recover-password/utils.tsx deleted file mode 100644 index 8b137891791f..000000000000 --- a/shared/constants/recover-password/utils.tsx +++ /dev/null @@ -1 +0,0 @@ - diff --git a/shared/constants/router2/util.tsx b/shared/constants/router2.tsx similarity index 98% rename from shared/constants/router2/util.tsx rename to shared/constants/router2.tsx index f47b6a479a5e..5da352fedcb7 100644 --- a/shared/constants/router2/util.tsx +++ b/shared/constants/router2.tsx @@ -1,6 +1,6 @@ import type * as React from 'react' -import type * as T from '../types' -import * as Tabs from '../tabs' +import type * as T from './types' +import * as Tabs from './tabs' import { StackActions, CommonActions, @@ -10,11 +10,11 @@ import { type NavigationState, } from '@react-navigation/core' import type {NavigateAppendType, RouteKeys, RootParamList as KBRootParamList} from '@/router-v2/route-params' -import type {GetOptionsRet} from '../types/router2' +import type {GetOptionsRet} from './types/router2' import {produce} from 'immer' import isEqual from 'lodash/isEqual' -import {isMobile, isTablet} from '../platform' -import {shallowEqual, type ViewPropsToPageProps} from '../utils' +import {isMobile, isTablet} from './platform' +import {shallowEqual, type ViewPropsToPageProps} from './utils' import {registerDebugClear} from '@/util/debug' export const navigationRef = createNavigationContainerRef() @@ -440,4 +440,3 @@ export const appendEncryptRecipientsBuilder = () => { selected: 'cryptoTeamBuilder', }) } - diff --git a/shared/constants/router2/index.tsx b/shared/constants/router2/index.tsx deleted file mode 100644 index 24c72281cad0..000000000000 --- a/shared/constants/router2/index.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import type * as T from '../types' -import * as Z from '@/util/zustand' -import * as Tabs from '../tabs' -import type {RouteKeys} from '@/router-v2/route-params' -import {storeRegistry} from '../store-registry' -import * as Util from './util' - -export { - type NavState, - getTab, - navigationRef, - getRootState, - _getNavigator, - logState, - getVisiblePath, - getModalStack, - getVisibleScreen, - navToProfile, - navToThread, - getRouteTab, - getRouteLoggedIn, - useSafeFocusEffect, - makeScreen, -} from './util' -export type {PathParam, Navigator} from './util' - -type Store = T.Immutable<{ - navState?: unknown -}> - -const initialStore: Store = { - navState: undefined, -} - -export interface State extends Store { - dispatch: { - clearModals: () => void - dynamic: { - tabLongPress?: (tab: string) => void - } - navigateAppend: (path: Util.PathParam, replace?: boolean) => void - navigateUp: () => void - navUpToScreen: (name: RouteKeys) => void - popStack: () => void - resetState: () => void - setNavState: (ns: Util.NavState) => void - switchTab: (tab: Tabs.AppTab) => void - } - appendEncryptRecipientsBuilder: () => void - appendNewChatBuilder: () => void - appendNewTeamBuilder: (teamID: T.Teams.TeamID) => void - appendPeopleBuilder: () => void -} - -export const useRouterState = Z.createZustand((set, get) => { - const dispatch: State['dispatch'] = { - clearModals: Util.clearModals, - dynamic: { - tabLongPress: undefined, - }, - navUpToScreen: Util.navUpToScreen, - navigateAppend: Util.navigateAppend, - navigateUp: Util.navigateUp, - popStack: Util.popStack, - resetState: () => { - set(s => ({ - ...s, - dispatch: s.dispatch, - })) - }, - setNavState: next => { - const DEBUG_NAV = __DEV__ && (false as boolean) - DEBUG_NAV && console.log('[Nav] setNavState') - const prev = get().navState as Util.NavState - if (prev === next) return - set(s => { - s.navState = next - }) - - const updateTeamBuilding = () => { - const namespaces = ['chat2', 'crypto', 'teams', 'people'] as const - const namespaceToRoute = new Map([ - ['chat2', 'chatNewChat'], - ['crypto', 'cryptoTeamBuilder'], - ['teams', 'teamsTeamBuilder'], - ['people', 'peopleTeamBuilder'], - ]) - for (const namespace of namespaces) { - const wasTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(prev)?.name - if (wasTeamBuilding) { - // team building or modal on top of that still - const isTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(next)?.name - if (!isTeamBuilding) { - storeRegistry.getTBStore(namespace).dispatch.cancelTeamBuilding() - } - } - } - } - updateTeamBuilding() - - const updateFS = () => { - // Clear critical update when we nav away from tab - if ( - prev && - Util.getTab(prev) === Tabs.fsTab && - next && - Util.getTab(next) !== Tabs.fsTab && - storeRegistry.getState('fs').criticalUpdate - ) { - const {dispatch} = storeRegistry.getState('fs') - dispatch.setCriticalUpdate(false) - } - const fsRrouteNames = ['fsRoot', 'barePreview'] - const wasScreen = fsRrouteNames.includes(Util.getVisibleScreen(prev)?.name ?? '') - const isScreen = fsRrouteNames.includes(Util.getVisibleScreen(next)?.name ?? '') - if (wasScreen !== isScreen) { - const {dispatch} = storeRegistry.getState('fs') - if (wasScreen) { - dispatch.userOut() - } else { - dispatch.userIn() - } - } - } - updateFS() - - const updateSignup = () => { - // Clear "just signed up email" when you leave the people tab after signup - if ( - prev && - Util.getTab(prev) === Tabs.peopleTab && - next && - Util.getTab(next) !== Tabs.peopleTab && - storeRegistry.getState('signup').justSignedUpEmail - ) { - storeRegistry.getState('signup').dispatch.clearJustSignedUpEmail() - } - } - updateSignup() - - const updatePeople = () => { - if (prev && Util.getTab(prev) === Tabs.peopleTab && next && Util.getTab(next) !== Tabs.peopleTab) { - storeRegistry.getState('people').dispatch.markViewed() - } - } - updatePeople() - - const updateTeams = () => { - if (prev && Util.getTab(prev) === Tabs.teamsTab && next && Util.getTab(next) !== Tabs.teamsTab) { - storeRegistry.getState('teams').dispatch.clearNavBadges() - } - } - updateTeams() - - const updateSettings = () => { - // Clear "check your inbox" in settings when you leave the settings tab - if ( - prev && - Util.getTab(prev) === Tabs.settingsTab && - next && - Util.getTab(next) !== Tabs.settingsTab && - storeRegistry.getState('settings-email').addedEmail - ) { - storeRegistry.getState('settings-email').dispatch.resetAddedEmail() - } - } - updateSettings() - - storeRegistry.getState('chat').dispatch.onRouteChanged(prev, next) - }, - switchTab: Util.switchTab, - } - - return { - ...initialStore, - appendEncryptRecipientsBuilder: Util.appendEncryptRecipientsBuilder, - appendNewChatBuilder: Util.appendNewChatBuilder, - appendNewTeamBuilder: Util.appendNewTeamBuilder, - appendPeopleBuilder: Util.appendPeopleBuilder, - dispatch, - } -}) diff --git a/shared/constants/rpc-utils.tsx b/shared/constants/rpc-utils.tsx index 5969509c5d03..8c5993554391 100644 --- a/shared/constants/rpc-utils.tsx +++ b/shared/constants/rpc-utils.tsx @@ -1,6 +1,12 @@ import * as T from './types' import {uint8ArrayToString} from 'uint8array-extras' -import {type Device} from './provision' + +type Device = { + deviceNumberOfType: number + id: T.Devices.DeviceID + name: string + type: T.Devices.DeviceType +} export const bodyToJSON = (body?: Uint8Array): unknown => { if (!body) return undefined @@ -27,4 +33,3 @@ export const rpcDeviceToDevice = (d: T.RPCGen.Device): Device => { throw new Error('Invalid device type detected: ' + type) } } - diff --git a/shared/constants/settings/util.tsx b/shared/constants/settings.tsx similarity index 78% rename from shared/constants/settings/util.tsx rename to shared/constants/settings.tsx index 281dc0582fd5..b54c44d6f26e 100644 --- a/shared/constants/settings/util.tsx +++ b/shared/constants/settings.tsx @@ -1,6 +1,3 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - export const traceInProgressKey = 'settings:traceInProgress' export const processorProfileInProgressKey = 'settings:processorProfileInProgress' @@ -47,17 +44,3 @@ export type SettingsTab = | typeof settingsCryptoTab | typeof settingsContactsTab | typeof settingsWhatsNewTab - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - case EngineGen.keybase1NotifyUsersPasswordChanged: - case EngineGen.keybase1NotifyPhoneNumberPhoneNumbersChanged: - case EngineGen.keybase1NotifyEmailAddressEmailsChanged: - { - storeRegistry.getState('settings').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/signup/util.tsx b/shared/constants/signup/util.tsx deleted file mode 100644 index 7c58483bdb13..000000000000 --- a/shared/constants/signup/util.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - { - storeRegistry.getState('signup').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/store-registry.tsx b/shared/constants/store-registry.tsx deleted file mode 100644 index f62f57f25e1f..000000000000 --- a/shared/constants/store-registry.tsx +++ /dev/null @@ -1,353 +0,0 @@ -// used to allow non-circular cross-calls between stores -// ONLY for zustand stores -import type * as T from './types' -import type * as TBType from './team-building' -import type * as ConvoStateType from './chat2/convostate' -import type {ConvoState} from './chat2/convostate' -import type {State as ActiveState, useActiveState} from './active' -import type {State as ArchiveState, useArchiveState} from './archive' -import type {State as AutoResetState, useAutoResetState} from './autoreset' -import type {State as AvatarState, useAvatarState} from '@/common-adapters/avatar/store' -import type {State as BotsState, useBotsState} from './bots' -import type {State as ChatState, useChatState} from './chat2' -import type {State as ConfigState, useConfigState} from './config' -import type {State as CryptoState, useCryptoState} from './crypto' -import type {State as CurrentUserState, useCurrentUserState} from './current-user' -import type {State as DaemonState, useDaemonState} from './daemon' -import type {State as DarkModeState, useDarkModeState} from './darkmode' -import type {State as DeepLinksState, useDeepLinksState} from './deeplinks' -import type {State as DevicesState, useDevicesState} from './devices' -import type {State as EngineState, useEngineState} from './engine' -import type {State as FollowersState, useFollowerState} from './followers' -import type {State as FSState, useFSState} from './fs' -import type {State as GitState, useGitState} from './git' -import type {State as LogoutState, useLogoutState} from './logout' -import type {State as NotificationsState, useNotifState} from './notifications' -import type {State as PeopleState, usePeopleState} from './people' -import type {State as PinentryState, usePinentryState} from './pinentry' -import type {State as ProfileState, useProfileState} from './profile' -import type {State as ProvisionState, useProvisionState} from './provision' -import type {State as PushState, usePushState} from './push' -import type {State as RecoverPasswordState, useState as useRecoverPasswordState} from './recover-password' -import type {State as RouterState, useRouterState} from './router2' -import type {State as SettingsState, useSettingsState} from './settings' -import type {State as SettingsChatState, useSettingsChatState} from './settings-chat' -import type {State as SettingsContactsState, useSettingsContactsState} from './settings-contacts' -import type {State as SettingsEmailState, useSettingsEmailState} from './settings-email' -import type {State as SettingsPasswordState, usePWState} from './settings-password' -import type {State as SettingsPhoneState, useSettingsPhoneState} from './settings-phone' -import type {State as SignupState, useSignupState} from './signup' -import type {State as TeamsState, useTeamsState} from './teams' -import type {State as Tracker2State, useTrackerState} from './tracker2' -import type {State as UnlockFoldersState, useUnlockFoldersState} from './unlock-folders' -import type {State as UsersState, useUsersState} from './users' -import type {State as WaitingState, useWaitingState} from './waiting' -import type {State as WhatsNewState, useWhatsNewState} from './whats-new' - -type StoreName = - | 'active' - | 'archive' - | 'autoreset' - | 'avatar' - | 'bots' - | 'chat' - | 'config' - | 'crypto' - | 'current-user' - | 'daemon' - | 'dark-mode' - | 'deeplinks' - | 'devices' - | 'engine' - | 'followers' - | 'fs' - | 'git' - | 'logout' - | 'notifications' - | 'people' - | 'pinentry' - | 'profile' - | 'provision' - | 'push' - | 'recover-password' - | 'router' - | 'settings' - | 'settings-chat' - | 'settings-contacts' - | 'settings-email' - | 'settings-password' - | 'settings-phone' - | 'signup' - | 'teams' - | 'tracker2' - | 'unlock-folders' - | 'users' - | 'waiting' - | 'whats-new' - -type StoreStates = { - active: ActiveState - archive: ArchiveState - autoreset: AutoResetState - avatar: AvatarState - bots: BotsState - chat: ChatState - config: ConfigState - crypto: CryptoState - 'current-user': CurrentUserState - daemon: DaemonState - 'dark-mode': DarkModeState - deeplinks: DeepLinksState - devices: DevicesState - engine: EngineState - followers: FollowersState - fs: FSState - git: GitState - logout: LogoutState - notifications: NotificationsState - people: PeopleState - pinentry: PinentryState - profile: ProfileState - provision: ProvisionState - push: PushState - 'recover-password': RecoverPasswordState - router: RouterState - settings: SettingsState - 'settings-chat': SettingsChatState - 'settings-contacts': SettingsContactsState - 'settings-email': SettingsEmailState - 'settings-password': SettingsPasswordState - 'settings-phone': SettingsPhoneState - signup: SignupState - teams: TeamsState - tracker2: Tracker2State - 'unlock-folders': UnlockFoldersState - users: UsersState - waiting: WaitingState - 'whats-new': WhatsNewState -} - -type StoreHooks = { - active: typeof useActiveState - archive: typeof useArchiveState - autoreset: typeof useAutoResetState - avatar: typeof useAvatarState - bots: typeof useBotsState - chat: typeof useChatState - config: typeof useConfigState - crypto: typeof useCryptoState - 'current-user': typeof useCurrentUserState - daemon: typeof useDaemonState - 'dark-mode': typeof useDarkModeState - deeplinks: typeof useDeepLinksState - devices: typeof useDevicesState - engine: typeof useEngineState - followers: typeof useFollowerState - fs: typeof useFSState - git: typeof useGitState - logout: typeof useLogoutState - notifications: typeof useNotifState - people: typeof usePeopleState - pinentry: typeof usePinentryState - profile: typeof useProfileState - provision: typeof useProvisionState - push: typeof usePushState - 'recover-password': typeof useRecoverPasswordState - router: typeof useRouterState - settings: typeof useSettingsState - 'settings-chat': typeof useSettingsChatState - 'settings-contacts': typeof useSettingsContactsState - 'settings-email': typeof useSettingsEmailState - 'settings-password': typeof usePWState - 'settings-phone': typeof useSettingsPhoneState - signup: typeof useSignupState - teams: typeof useTeamsState - tracker2: typeof useTrackerState - 'unlock-folders': typeof useUnlockFoldersState - users: typeof useUsersState - waiting: typeof useWaitingState - 'whats-new': typeof useWhatsNewState -} - -class StoreRegistry { - getStore(storeName: T): StoreHooks[T] { - /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */ - switch (storeName) { - case 'active': { - const {useActiveState} = require('./active') - return useActiveState - } - case 'archive': { - const {useArchiveState} = require('./archive') - return useArchiveState - } - case 'autoreset': { - const {useAutoResetState} = require('./autoreset') - return useAutoResetState - } - case 'avatar': { - const {useAvatarState} = require('@/common-adapters/avatar/store') - return useAvatarState - } - case 'bots': { - const {useBotsState} = require('./bots') - return useBotsState - } - case 'chat': { - const {useChatState} = require('./chat2') - return useChatState - } - case 'config': { - const {useConfigState} = require('./config') - return useConfigState - } - case 'current-user': { - const {useCurrentUserState} = require('./current-user') - return useCurrentUserState - } - case 'crypto': { - const {useCryptoState} = require('./crypto') - return useCryptoState - } - case 'daemon': { - const {useDaemonState} = require('./daemon') - return useDaemonState - } - case 'dark-mode': { - const {useDarkModeState} = require('./darkmode') - return useDarkModeState - } - case 'deeplinks': { - const {useDeepLinksState} = require('./deeplinks') - return useDeepLinksState - } - case 'devices': { - const {useDevicesState} = require('./devices') - return useDevicesState - } - case 'engine': { - const {useEngineState} = require('./engine') - return useEngineState - } - case 'followers': { - const {useFollowerState} = require('./followers') - return useFollowerState - } - case 'fs': { - const {useFSState} = require('./fs') - return useFSState - } - case 'git': { - const {useGitState} = require('./git') - return useGitState - } - case 'logout': { - const {useLogoutState} = require('./logout') - return useLogoutState - } - case 'notifications': { - const {useNotifState} = require('./notifications') - return useNotifState - } - case 'people': { - const {usePeopleState} = require('./people') - return usePeopleState - } - case 'pinentry': { - const {usePinentryState} = require('./pinentry') - return usePinentryState - } - case 'profile': { - const {useProfileState} = require('./profile') - return useProfileState - } - case 'provision': { - const {useProvisionState} = require('./provision') - return useProvisionState - } - case 'push': { - const {usePushState} = require('./push') - return usePushState - } - case 'recover-password': { - const {useState} = require('./recover-password') - return useState - } - case 'router': { - const {useRouterState} = require('./router2') - return useRouterState - } - case 'settings': { - const {useSettingsState} = require('./settings') - return useSettingsState - } - case 'settings-chat': { - const {useSettingsChatState} = require('./settings-chat') - return useSettingsChatState - } - case 'settings-contacts': { - const {useSettingsContactsState} = require('./settings-contacts') - return useSettingsContactsState - } - case 'settings-email': { - const {useSettingsEmailState} = require('./settings-email') - return useSettingsEmailState - } - case 'settings-password': { - const {usePWState} = require('./settings-password') - return usePWState - } - case 'settings-phone': { - const {useSettingsPhoneState} = require('./settings-phone') - return useSettingsPhoneState - } - case 'signup': { - const {useSignupState} = require('./signup') - return useSignupState - } - case 'teams': { - const {useTeamsState} = require('./teams') - return useTeamsState - } - case 'tracker2': { - const {useTrackerState} = require('./tracker2') - return useTrackerState - } - case 'unlock-folders': { - const {useUnlockFoldersState} = require('./unlock-folders') - return useUnlockFoldersState - } - case 'users': { - const {useUsersState} = require('./users') - return useUsersState - } - case 'waiting': { - const {useWaitingState} = require('./waiting') - return useWaitingState - } - case 'whats-new': { - const {useWhatsNewState} = require('./whats-new') - return useWhatsNewState - } - default: - throw new Error(`Unknown store: ${storeName}`) - } - } - - getState(storeName: T): StoreStates[T] { - return this.getStore(storeName).getState() as StoreStates[T] - } - - getTBStore(name: T.TB.AllowedNamespace): TBType.State { - const {createTBStore} = require('./team-building') as typeof TBType - const store = createTBStore(name) - return store.getState() - } - - getConvoState(id: T.Chat.ConversationIDKey): ConvoState { - const {getConvoState} = require('./chat2/convostate') as typeof ConvoStateType - return getConvoState(id) - } -} - -export const storeRegistry = new StoreRegistry() diff --git a/shared/constants/strings.tsx b/shared/constants/strings.tsx index 5fe715a7795c..cceff363d6c6 100644 --- a/shared/constants/strings.tsx +++ b/shared/constants/strings.tsx @@ -44,6 +44,8 @@ export const waitingKeyRecoverPassword = 'recover-password:waiting' export const waitingKeyCrypto = 'cryptoWaiting' +export const searchWaitingKey = 'teamBuilding:search' + export const waitingKeyTeamsLoaded = 'teams:loaded' export const waitingKeyTeamsJoinTeam = 'teams:joinTeam' export const waitingKeyTeamsTeam = (teamID: T.Teams.TeamID) => `team:${teamID}` @@ -96,6 +98,13 @@ export const waitingKeyFSStat = 'fs:stat' export const waitingKeyFSCommitEdit = 'fs:commitEditWaitingKey' export const waitingKeyFSSetSyncOnCellular = 'fs:setSyncOnCellular' +export const loadAccountsWaitingKey = 'wallets:loadAccounts' + +export const currentVersion: string = '5.5.0' +export const lastVersion: string = '5.4.0' +export const lastLastVersion: string = '5.3.0' +export const keybaseFM = 'Keybase FM 87.7' + export const waitingKeyGitLoading = 'git:loading' export const waitingKeyUsersGetUserBlocks = 'users:getUserBlocks' diff --git a/shared/constants/team-building/utils.tsx b/shared/constants/team-building.tsx similarity index 82% rename from shared/constants/team-building/utils.tsx rename to shared/constants/team-building.tsx index e277db5d587f..24e3c9d457e7 100644 --- a/shared/constants/team-building/utils.tsx +++ b/shared/constants/team-building.tsx @@ -1,4 +1,4 @@ -import type * as T from '../types' +import type * as T from './types' const searchServices: Array = ['keybase', 'twitter', 'github', 'reddit', 'hackernews'] @@ -16,5 +16,3 @@ export const selfToUser = (you: string): T.TB.User => ({ serviceMap: {}, username: you, }) - -export const searchWaitingKey = 'teamBuilding:search' diff --git a/shared/constants/teams/util.tsx b/shared/constants/teams.tsx similarity index 82% rename from shared/constants/teams/util.tsx rename to shared/constants/teams.tsx index 48167c472d34..5eb4fbea86ff 100644 --- a/shared/constants/teams/util.tsx +++ b/shared/constants/teams.tsx @@ -1,29 +1,6 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' -import {storeRegistry} from '../store-registry' +import * as T from './types' import invert from 'lodash/invert' -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.chat1ChatUiChatShowManageChannels: - case EngineGen.keybase1NotifyTeamTeamMetadataUpdate: - case EngineGen.chat1NotifyChatChatWelcomeMessageLoaded: - case EngineGen.keybase1NotifyTeamTeamTreeMembershipsPartial: - case EngineGen.keybase1NotifyTeamTeamTreeMembershipsDone: - case EngineGen.keybase1NotifyTeamTeamRoleMapChanged: - case EngineGen.keybase1NotifyTeamTeamChangedByID: - case EngineGen.keybase1NotifyTeamTeamDeleted: - case EngineGen.keybase1NotifyTeamTeamExit: - case EngineGen.keybase1NotifyBadgesBadgeState: - case EngineGen.keybase1GregorUIPushState: - { - storeRegistry.getState('teams').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} - export const makeRetentionPolicy = ( r?: Partial ): T.Retention.RetentionPolicy => ({ diff --git a/shared/constants/tracker2/util.tsx b/shared/constants/tracker2/util.tsx deleted file mode 100644 index e6adb67ec4c6..000000000000 --- a/shared/constants/tracker2/util.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterIdentify3UIRpcPromise() - logger.info('Registered identify ui') - } catch (error) { - logger.warn('error in registering identify ui: ', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyTrackingTrackingChanged: - case EngineGen.keybase1Identify3UiIdentify3Result: - case EngineGen.keybase1Identify3UiIdentify3ShowTracker: - case EngineGen.keybase1NotifyUsersUserChanged: - case EngineGen.keybase1NotifyTrackingNotifyUserBlocked: - case EngineGen.keybase1Identify3UiIdentify3UpdateRow: - case EngineGen.keybase1Identify3UiIdentify3UserReset: - case EngineGen.keybase1Identify3UiIdentify3UpdateUserCard: - case EngineGen.keybase1Identify3UiIdentify3Summary: - { - storeRegistry.getState('tracker2').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/types/chat2/index.tsx b/shared/constants/types/chat2/index.tsx index 212ae677e11b..9742dd2548ca 100644 --- a/shared/constants/types/chat2/index.tsx +++ b/shared/constants/types/chat2/index.tsx @@ -5,11 +5,6 @@ import * as _Message from './message' import type * as Meta from './meta' import {uint8ArrayToHex, hexToUint8Array} from 'uint8array-extras' -export type PaymentConfirmInfo = { - error?: RPCTypes.Status - summary?: T.RPCChat.UIChatPaymentSummary -} - // Static config data we use for various things export type StaticConfig = { deletableByDeleteHistory: Set<_Message.MessageType> @@ -86,11 +81,6 @@ export type AttachmentViewInfo = { last: boolean } -export type AttachmentFullscreenSelection = { - autoPlay: boolean - message: _Message.Message -} - export type CommandStatusInfo = { displayText: string displayType: T.RPCChat.UICommandStatusDisplayTyp @@ -208,4 +198,3 @@ export const outboxIDToRpcOutboxID = (outboxID: _Message.OutboxID): T.RPCChat.Ou export * from './message' export * from './common' export type * from './meta' -export type * from './rowitem' diff --git a/shared/constants/types/chat2/message.tsx b/shared/constants/types/chat2/message.tsx index f7b7d1332f81..16a90713c8bb 100644 --- a/shared/constants/types/chat2/message.tsx +++ b/shared/constants/types/chat2/message.tsx @@ -51,11 +51,6 @@ export const outboxIDToString = (o: OutboxID): string => o export type MentionsAt = ReadonlySet export type MentionsChannel = 'none' | 'all' | 'here' -export interface MessageExplodeDescription { - text: string - seconds: number -} - export interface PathAndOutboxID { path: string outboxID?: T.RPCChat.OutboxID diff --git a/shared/constants/types/config.tsx b/shared/constants/types/config.tsx index e001a72201e9..837ec64ebb5e 100644 --- a/shared/constants/types/config.tsx +++ b/shared/constants/types/config.tsx @@ -1,5 +1,3 @@ -import type * as NetInfo from '@react-native-community/netinfo' - export type OutOfDate = { critical: boolean message: string @@ -8,8 +6,7 @@ export type OutOfDate = { } export type DaemonHandshakeState = 'starting' | 'waitingForWaiters' | 'done' export type ConfiguredAccount = { + fullname?: string hasStoredSecret: boolean username: string } -// 'notavailable' is the desktop default -export type ConnectionType = NetInfo.NetInfoStateType | 'notavailable' diff --git a/shared/constants/types/crypto.tsx b/shared/constants/types/crypto.tsx index db2f4c451ffb..ad568add5e73 100644 --- a/shared/constants/types/crypto.tsx +++ b/shared/constants/types/crypto.tsx @@ -1,4 +1,2 @@ -export type TextType = 'cipher' | 'plain' export type Operations = 'encrypt' | 'decrypt' | 'sign' | 'verify' export type InputTypes = 'text' | 'file' -export type OutputType = 'text' | 'file' diff --git a/shared/constants/types/git.tsx b/shared/constants/types/git.tsx index 3a0e54f885d1..c960905a7cdf 100644 --- a/shared/constants/types/git.tsx +++ b/shared/constants/types/git.tsx @@ -1,4 +1,3 @@ -import type * as T from '@/constants/types' export type GitInfo = { canDelete: boolean channelName?: string @@ -12,9 +11,3 @@ export type GitInfo = { teamname?: string url: string } - -export type State = T.Immutable<{ - readonly error?: Error - readonly idToInfo: Map - readonly isNew?: Set -}> diff --git a/shared/constants/types/push.tsx b/shared/constants/types/push.tsx index 8b35307b1543..ea0e33b9a4cc 100644 --- a/shared/constants/types/push.tsx +++ b/shared/constants/types/push.tsx @@ -1,8 +1,6 @@ import type * as ChatTypes from './chat2' import type * as RPCChatTypes from './rpc-chat-gen' -export type TokenType = 'apple' | 'appledev' | 'androidplay' - export type PushNotification = | { badges: number diff --git a/shared/constants/types/wallets.tsx b/shared/constants/types/wallets.tsx index 0be9a9ab413d..4c4b5488b782 100644 --- a/shared/constants/types/wallets.tsx +++ b/shared/constants/types/wallets.tsx @@ -1,10 +1,5 @@ import type * as StellarRPCTypes from './rpc-stellar-gen' -export type Reserve = { - amount: string - description: string // e.g. 'account' or 'KEYZ/keybase.io trust line' -} - export type AccountID = string export const noAccountID = 'NOACCOUNTID' export type PaymentID = StellarRPCTypes.PaymentID diff --git a/shared/constants/unlock-folders/util.tsx b/shared/constants/unlock-folders/util.tsx deleted file mode 100644 index 89d12305fd44..000000000000 --- a/shared/constants/unlock-folders/util.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterRekeyUIRpcPromise() - logger.info('Registered rekey ui') - } catch (error) { - logger.warn('error in registering rekey ui: ') - logger.debug('error in registering rekey ui: ', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1RekeyUIRefresh: - case EngineGen.keybase1RekeyUIDelegateRekeyUI: - { - storeRegistry.getState('unlock-folders').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/users/util.tsx b/shared/constants/users/util.tsx deleted file mode 100644 index cd70c647ccb5..000000000000 --- a/shared/constants/users/util.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyUsersIdentifyUpdate: - case EngineGen.keybase1NotifyTrackingNotifyUserBlocked: - { - storeRegistry.getState('users').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/values.tsx b/shared/constants/values.tsx index ddf79f1e2170..31ec39ad5cf5 100644 --- a/shared/constants/values.tsx +++ b/shared/constants/values.tsx @@ -1,2 +1,11 @@ export const maxHandshakeTries = 3 export const maxUsernameLength = 16 + +// Exit Codes +export const ExitCodeFuseKextError = 4 +export const ExitCodeFuseKextPermissionError = 5 +export const ExitCodeAuthCanceledError = 6 +// See Installer.m: KBExitFuseCriticalUpdate +export const ExitFuseCriticalUpdate = 8 +// See install_darwin.go: exitCodeFuseCriticalUpdateFailed +export const ExitFuseCriticalUpdateFailed = 300 diff --git a/shared/constants/wallets/utils.tsx b/shared/constants/wallets/utils.tsx deleted file mode 100644 index 6754f164b02d..000000000000 --- a/shared/constants/wallets/utils.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export const loadAccountsWaitingKey = 'wallets:loadAccounts' - diff --git a/shared/constants/whats-new/index.tsx b/shared/constants/whats-new/index.tsx deleted file mode 100644 index 526d9e4473d7..000000000000 --- a/shared/constants/whats-new/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type * as T from '../types' -import * as Z from '@/util/zustand' -import {uint8ArrayToString} from 'uint8array-extras' -import {noVersion, getSeenVersions} from './utils' - -export {currentVersion, lastVersion, lastLastVersion, keybaseFM} from './utils' - -type SeenVersionsMap = {[key in string]: boolean} - -type Store = T.Immutable<{ - lastSeenVersion: string - seenVersions: SeenVersionsMap -}> -const initialStore: Store = { - lastSeenVersion: '', - seenVersions: getSeenVersions(''), -} -export interface State extends Store { - dispatch: { - resetState: 'default' - updateLastSeen: (lastSeenItem?: {md: T.RPCGen.Gregor1.Metadata; item: T.RPCGen.Gregor1.Item}) => void - } - anyVersionsUnseen: () => boolean -} -export const useWhatsNewState = Z.createZustand((set, get) => { - const dispatch: State['dispatch'] = { - resetState: 'default', - updateLastSeen: lastSeenItem => { - if (lastSeenItem) { - const {body} = lastSeenItem.item - const pushStateLastSeenVersion = uint8ArrayToString(body) - const lastSeenVersion = pushStateLastSeenVersion || noVersion - // Default to 0.0.0 (noVersion) if user has never marked a version as seen - set(s => { - s.lastSeenVersion = lastSeenVersion - s.seenVersions = getSeenVersions(lastSeenVersion) - }) - } else { - set(s => { - s.lastSeenVersion = noVersion - s.seenVersions = getSeenVersions(noVersion) - }) - } - }, - } - return { - ...initialStore, - anyVersionsUnseen: () => { - const {lastSeenVersion: ver} = get() - // On first load of what's new, lastSeenVersion == noVersion so everything is unseen - return ver !== '' && ver === noVersion ? true : Object.values(getSeenVersions(ver)).some(seen => !seen) - }, - dispatch, - } -}) diff --git a/shared/constants/whats-new/utils.tsx b/shared/constants/whats-new/utils.tsx deleted file mode 100644 index bc3a3e93f98e..000000000000 --- a/shared/constants/whats-new/utils.tsx +++ /dev/null @@ -1,69 +0,0 @@ -const semver = { - gte: (a: string, b: string) => { - const arra = a.split('.').map(i => parseInt(i)) - const [a1, a2, a3] = arra - const arrb = b.split('.').map(i => parseInt(i)) - const [b1, b2, b3] = arrb - if (arra.length === 3 && arrb.length === 3) { - return a1! >= b1! && a2! >= b2! && a3! >= b3! - } else { - return false - } - }, - valid: (v: string) => - v.split('.').reduce((cnt, i) => { - if (parseInt(i) >= 0) { - return cnt + 1 - } - return cnt - }, 0) === 3, -} - -const noVersion: string = '0.0.0' -export const currentVersion: string = '5.5.0' -export const lastVersion: string = '5.4.0' -export const lastLastVersion: string = '5.3.0' -const versions = [currentVersion, lastVersion, lastLastVersion, noVersion] as const -export const keybaseFM = 'Keybase FM 87.7' - -type SeenVersionsMap = {[key in string]: boolean} - -const isVersionValid = (version: string) => { - return version ? semver.valid(version) : false -} - -export const getSeenVersions = (lastSeenVersion: string): SeenVersionsMap => { - const initialMap: SeenVersionsMap = { - [currentVersion]: true, - [lastLastVersion]: true, - [lastVersion]: true, - [noVersion]: true, - } - - if (!lastSeenVersion || !semver.valid(lastSeenVersion)) { - return initialMap - } - if (lastSeenVersion === noVersion) { - return { - [currentVersion]: false, - [lastLastVersion]: false, - [lastVersion]: false, - [noVersion]: false, - } - } - - const validVersions = versions.filter(isVersionValid) - - const seenVersions = validVersions.reduce( - (acc, version) => ({ - ...acc, - [version]: version === noVersion ? true : semver.gte(lastSeenVersion, version), - }), - initialMap - ) - - return seenVersions -} - -export {noVersion} - diff --git a/shared/crypto/input.tsx b/shared/crypto/input.tsx index 575de4122740..273288164961 100644 --- a/shared/crypto/input.tsx +++ b/shared/crypto/input.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as React from 'react' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' diff --git a/shared/crypto/operations/decrypt.tsx b/shared/crypto/operations/decrypt.tsx index 4f553f9e7b06..61060317259c 100644 --- a/shared/crypto/operations/decrypt.tsx +++ b/shared/crypto/operations/decrypt.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as React from 'react' import {Input, DragAndDrop, InputActionsBar, OperationBanner} from '../input' diff --git a/shared/crypto/operations/encrypt.tsx b/shared/crypto/operations/encrypt.tsx index 856c4b40cb0c..7be3f776cec8 100644 --- a/shared/crypto/operations/encrypt.tsx +++ b/shared/crypto/operations/encrypt.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as React from 'react' import Recipients from '../recipients' diff --git a/shared/crypto/operations/sign.tsx b/shared/crypto/operations/sign.tsx index 5ec68a46802e..7b0267c7f2a5 100644 --- a/shared/crypto/operations/sign.tsx +++ b/shared/crypto/operations/sign.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as React from 'react' import * as Kb from '@/common-adapters' import openURL from '@/util/open-url' diff --git a/shared/crypto/operations/verify.tsx b/shared/crypto/operations/verify.tsx index 3b64988b45a3..56decc162d9d 100644 --- a/shared/crypto/operations/verify.tsx +++ b/shared/crypto/operations/verify.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as React from 'react' import {Input, InputActionsBar, DragAndDrop, OperationBanner} from '../input' diff --git a/shared/crypto/output.tsx b/shared/crypto/output.tsx index d349ae39efe5..6510f136f84b 100644 --- a/shared/crypto/output.tsx +++ b/shared/crypto/output.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Crypto from '@/constants/crypto' +import * as Chat from '@/stores/chat2' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as Path from '@/util/path' import * as React from 'react' @@ -8,9 +8,9 @@ import capitalize from 'lodash/capitalize' import type * as T from '@/constants/types' import {pickFiles} from '@/util/pick-files' import type HiddenString from '@/util/hidden-string' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' import * as FS from '@/constants/fs' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' type OutputProps = {operation: T.Crypto.Operations} type OutputActionsBarProps = {operation: T.Crypto.Operations} @@ -181,7 +181,7 @@ export const OutputActionsBar = (props: OutputActionsBarProps) => { const actionsDisabled = waiting || !outputValid const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = () => { openLocalPathInSystemFileManagerDesktop?.(output.stringValue()) @@ -194,7 +194,7 @@ export const OutputActionsBar = (props: OutputActionsBarProps) => { previewConversation({participants: [username.stringValue()], reason: 'search'}) } - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopyOutput = () => { copyToClipboard(output.stringValue()) } @@ -369,7 +369,7 @@ export const OperationOutput = (props: OutputProps) => { const output = _output.stringValue() const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = () => { if (!output) return diff --git a/shared/crypto/recipients.tsx b/shared/crypto/recipients.tsx index a4ad222b2a2d..a7ac327df09f 100644 --- a/shared/crypto/recipients.tsx +++ b/shared/crypto/recipients.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' const placeholder = 'Search people' diff --git a/shared/crypto/routes.tsx b/shared/crypto/routes.tsx index da93e894f5bb..14200ecfdaf5 100644 --- a/shared/crypto/routes.tsx +++ b/shared/crypto/routes.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Crypto from '@/constants/crypto/util' +import * as Crypto from '@/constants/crypto' import {HeaderLeftCancel2, type HeaderBackButtonProps} from '@/common-adapters/header-hoc' import cryptoTeamBuilder from '../team-building/page' diff --git a/shared/crypto/sub-nav/index.desktop.tsx b/shared/crypto/sub-nav/index.desktop.tsx index 7cd6cdfdd883..47e4b97a6486 100644 --- a/shared/crypto/sub-nav/index.desktop.tsx +++ b/shared/crypto/sub-nav/index.desktop.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Kb from '@/common-adapters' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Common from '@/router-v2/common.desktop' import LeftNav from './left-nav.desktop' import { diff --git a/shared/crypto/sub-nav/index.native.tsx b/shared/crypto/sub-nav/index.native.tsx index c41261148096..f2ee824bec3b 100644 --- a/shared/crypto/sub-nav/index.native.tsx +++ b/shared/crypto/sub-nav/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import NavRow from './nav-row' diff --git a/shared/crypto/sub-nav/left-nav.desktop.tsx b/shared/crypto/sub-nav/left-nav.desktop.tsx index e44a26459acd..5dc1b78e412a 100644 --- a/shared/crypto/sub-nav/left-nav.desktop.tsx +++ b/shared/crypto/sub-nav/left-nav.desktop.tsx @@ -1,6 +1,6 @@ import type * as React from 'react' import * as Kb from '@/common-adapters' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import NavRow from './nav-row' type Row = (typeof Crypto.Tabs)[number] & { diff --git a/shared/deeplinks/error.tsx b/shared/deeplinks/error.tsx index ed8a9cda94c5..6450b1b88b1f 100644 --- a/shared/deeplinks/error.tsx +++ b/shared/deeplinks/error.tsx @@ -1,6 +1,5 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useDeepLinksState} from '@/constants/deeplinks' type KeybaseLinkErrorBodyProps = { message: string @@ -21,9 +20,9 @@ export const KeybaseLinkErrorBody = (props: KeybaseLinkErrorBodyProps) => { ) } -const KeybaseLinkError = () => { - const deepError = useDeepLinksState(s => s.keybaseLinkError) - const message = deepError +const LinkError = (props: {error?: string}) => { + const error = props.error ?? 'Invalid page! (sorry)' + const message = error const isError = true const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const onClose = () => navigateUp() @@ -48,4 +47,6 @@ const styles = Kb.Styles.styleSheetCreate(() => ({ }), })) -export default KeybaseLinkError +type OwnProps = C.ViewPropsToPageProps +const Screen = (p: OwnProps) => +export default Screen diff --git a/shared/desktop/app/installer.desktop.tsx b/shared/desktop/app/installer.desktop.tsx index 3d6306fcbc41..3d4b50dd5e73 100644 --- a/shared/desktop/app/installer.desktop.tsx +++ b/shared/desktop/app/installer.desktop.tsx @@ -9,6 +9,13 @@ import {ctlQuit} from './ctl.desktop' import {isDarwin} from '@/constants/platform' import logger from '@/logger' import zlib from 'zlib' +import { + ExitCodeAuthCanceledError, + ExitCodeFuseKextError, + ExitCodeFuseKextPermissionError, + ExitFuseCriticalUpdate, + ExitFuseCriticalUpdateFailed, +} from '@/constants/values' const file = path.join(Electron.app.getPath('userData'), 'installer.json') @@ -56,18 +63,6 @@ type ResultType = | undefined const checkErrors = (result: ResultType, errors: Array, errorTypes: ErrorTypes) => { - // Copied from old constants/favorite.js - // See Installer.m: KBExitFuseKextError - const ExitCodeFuseKextError = 4 - // See Installer.m: KBExitFuseKextPermissionError - const ExitCodeFuseKextPermissionError = 5 - // See Installer.m: KBExitAuthCanceledError - const ExitCodeAuthCanceledError = 6 - // See Installer.m: KBExitFuseCriticalUpdate - const ExitFuseCriticalUpdate = 8 - // See install_darwin.go: exitCodeFuseCriticalUpdateFailed - const ExitFuseCriticalUpdateFailed = 300 - const results = result?.componentResults || [] results.forEach(cr => { if (cr.status?.code === 0) { diff --git a/shared/desktop/app/menu-bar.desktop.tsx b/shared/desktop/app/menu-bar.desktop.tsx index cbc76a7e902f..bca0da6b42de 100644 --- a/shared/desktop/app/menu-bar.desktop.tsx +++ b/shared/desktop/app/menu-bar.desktop.tsx @@ -8,7 +8,7 @@ import {menubar} from 'menubar' import {showDevTools, skipSecondaryDevtools} from '@/local-debug' import {getMainWindow} from './main-window.desktop' import {assetRoot, htmlPrefix} from './html-root.desktop' -import type {BadgeType} from '@/constants/notifications' +import type {BadgeType} from '@/stores/notifications' const getIcons = (iconType: BadgeType, badges: number) => { const size = isWindows ? 16 : 22 diff --git a/shared/desktop/app/node2.desktop.tsx b/shared/desktop/app/node2.desktop.tsx index 7d3b6c8db307..6b65e04f06ce 100644 --- a/shared/desktop/app/node2.desktop.tsx +++ b/shared/desktop/app/node2.desktop.tsx @@ -427,8 +427,7 @@ const plumbEvents = () => { () => {}, (c: boolean) => { R.remoteDispatch(RemoteGen.createEngineConnection({connected: c})) - }, - false + } ) const timeoutPromise = async (timeMs: number) => diff --git a/shared/desktop/remote/use-serialize-props.desktop.tsx b/shared/desktop/remote/use-serialize-props.desktop.tsx index 4bd25aacfdf1..3327ea8f6428 100644 --- a/shared/desktop/remote/use-serialize-props.desktop.tsx +++ b/shared/desktop/remote/use-serialize-props.desktop.tsx @@ -5,7 +5,7 @@ import * as React from 'react' import * as C from '@/constants' import KB2 from '@/util/electron.desktop' import isEqual from 'lodash/isEqual' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const {rendererNewProps} = KB2.functions diff --git a/shared/desktop/renderer/main.desktop.tsx b/shared/desktop/renderer/main.desktop.tsx index 640693e7a1bd..f216bfdfa816 100644 --- a/shared/desktop/renderer/main.desktop.tsx +++ b/shared/desktop/renderer/main.desktop.tsx @@ -3,7 +3,7 @@ import './globals.desktop' import {isDarwin, isWindows} from '@/constants/platform' import '@/util/why-did-you-render' import KB2, {waitOnKB2Loaded} from '@/util/electron.desktop' -import * as DarkMode from '@/constants/darkmode' +import * as DarkMode from '@/stores/darkmode' waitOnKB2Loaded(() => { const {setSystemSupported, setSystemDarkMode} = DarkMode.useDarkModeState.getState().dispatch diff --git a/shared/desktop/renderer/main2.desktop.tsx b/shared/desktop/renderer/main2.desktop.tsx index 969a0917143b..e2d29cd73822 100644 --- a/shared/desktop/renderer/main2.desktop.tsx +++ b/shared/desktop/renderer/main2.desktop.tsx @@ -4,24 +4,229 @@ import Main from '@/app/main.desktop' import * as C from '@/constants' import * as React from 'react' import * as ReactDOM from 'react-dom/client' -import type * as RemoteGen from '@/actions/remote-gen' +import * as RemoteGen from '@/actions/remote-gen' import Root from './container.desktop' import {makeEngine} from '@/engine' import {disableDragDrop} from '@/util/drag-drop.desktop' -import {dumpLogs} from '@/constants/platform-specific/index.desktop' import {initDesktopStyles} from '@/styles/index.desktop' import {isWindows} from '@/constants/platform' import KB2 from '@/util/electron.desktop' +import {ignorePromise} from '@/constants/utils' +import {useConfigState} from '@/stores/config' +import {usePinentryState} from '@/stores/pinentry' +import * as T from '@/constants/types' +import {RPCError} from '@/util/errors' +import {switchTab} from '@/constants/router2' +import {storeRegistry} from '@/stores/store-registry' +import {onEngineConnected, onEngineDisconnected} from '@/constants/init/index.desktop' +import {handleAppLink} from '@/constants/deeplinks' +import * as Crypto from '@/constants/crypto' +import * as Tabs from '@/constants/tabs' +import {isPathSaltpackEncrypted, isPathSaltpackSigned} from '@/util/path' +import type HiddenString from '@/util/hidden-string' +import {useCryptoState} from '@/stores/crypto' +import logger from '@/logger' import {debugWarning} from '@/util/debug-warning' -import {useConfigState} from '@/constants/config' import type {default as NewMainType} from '../../app/main.desktop' import {setServiceDecoration} from '@/common-adapters/markdown/react' import ServiceDecoration from '@/common-adapters/markdown/service-decoration' -import {useDarkModeState} from '@/constants/darkmode' -import {initPlatformListener} from '@/constants/platform-specific' +import {useDarkModeState} from '@/stores/darkmode' +import {initPlatformListener, onEngineIncoming} from '@/constants/init/index.desktop' setServiceDecoration(ServiceDecoration) -const {ipcRendererOn, requestWindowsStartService, appStartedUp} = KB2.functions +const {ipcRendererOn, requestWindowsStartService, appStartedUp, ctlQuit, dumpNodeLogger} = KB2.functions + +const handleSaltPackOpen = (_path: string | HiddenString) => { + const path = typeof _path === 'string' ? _path : _path.stringValue() + + if (!useConfigState.getState().loggedIn) { + console.warn('Tried to open a saltpack file before being logged in') + return + } + let operation: T.Crypto.Operations | undefined + if (isPathSaltpackEncrypted(path)) { + operation = Crypto.Operations.Decrypt + } else if (isPathSaltpackSigned(path)) { + operation = Crypto.Operations.Verify + } else { + logger.warn( + 'Deeplink received saltpack file path not ending in ".encrypted.saltpack" or ".signed.saltpack"' + ) + return + } + useCryptoState.getState().dispatch.onSaltpackOpenFile(operation, path) + switchTab(Tabs.cryptoTab) +} + +const dumpLogs = async (reason?: string) => { + await logger.dump() + await (dumpNodeLogger?.() ?? Promise.resolve([])) + // quit as soon as possible + if (reason === 'quitting through menu') { + ctlQuit?.() + } +} + +const updateApp = () => { + const f = async () => { + await T.RPCGen.configStartUpdateIfNeededRpcPromise() + } + ignorePromise(f()) + // * If user choose to update: + // We'd get killed and it doesn't matter what happens here. + // * If user hits "Ignore": + // Note that we ignore the snooze here, so the state shouldn't change, + // and we'd back to where we think we still need an update. So we could + // have just unset the "updating" flag.However, in case server has + // decided to pull out the update between last time we asked the updater + // and now, we'd be in a wrong state if we didn't check with the service. + // Since user has interacted with it, we still ask the service to make + // sure. + + useConfigState.getState().dispatch.setUpdating() +} + +const eventFromRemoteWindows = (action: RemoteGen.Actions) => { + switch (action.type) { + case RemoteGen.resetStore: + break + case RemoteGen.openChatFromWidget: { + useConfigState.getState().dispatch.showMain() + storeRegistry.getConvoState(action.payload.conversationIDKey).dispatch.navigateToThread('inboxSmall') + break + } + case RemoteGen.inboxRefresh: { + storeRegistry.getState('chat').dispatch.inboxRefresh('widgetRefresh') + break + } + case RemoteGen.engineConnection: { + if (action.payload.connected) { + onEngineConnected() + } else { + onEngineDisconnected() + } + break + } + case RemoteGen.switchTab: { + switchTab(action.payload.tab) + break + } + case RemoteGen.setCriticalUpdate: { + storeRegistry.getState('fs').dispatch.setCriticalUpdate(action.payload.critical) + break + } + case RemoteGen.userFileEditsLoad: { + storeRegistry.getState('fs').dispatch.userFileEditsLoad() + break + } + case RemoteGen.openFilesFromWidget: { + storeRegistry.getState('fs').dispatch.defer.openFilesFromWidgetDesktop?.(action.payload.path) + break + } + case RemoteGen.saltpackFileOpen: { + handleSaltPackOpen(action.payload.path) + break + } + case RemoteGen.pinentryOnCancel: { + usePinentryState.getState().dispatch.dynamic.onCancel?.() + break + } + case RemoteGen.pinentryOnSubmit: { + usePinentryState.getState().dispatch.dynamic.onSubmit?.(action.payload.password) + break + } + case RemoteGen.openPathInSystemFileManager: { + storeRegistry.getState('fs').dispatch.defer.openPathInSystemFileManagerDesktop?.(action.payload.path) + break + } + case RemoteGen.unlockFoldersSubmitPaperKey: { + T.RPCGen.loginPaperKeySubmitRpcPromise({paperPhrase: action.payload.paperKey}, 'unlock-folders:waiting') + .then(() => { + useConfigState.getState().dispatch.openUnlockFolders([]) + }) + .catch((e: unknown) => { + if (!(e instanceof RPCError)) return + useConfigState.setState(s => { + s.unlockFoldersError = e.desc + }) + }) + break + } + case RemoteGen.closeUnlockFolders: { + T.RPCGen.rekeyRekeyStatusFinishRpcPromise() + .then(() => {}) + .catch(() => {}) + useConfigState.getState().dispatch.openUnlockFolders([]) + break + } + case RemoteGen.stop: { + storeRegistry.getState('settings').dispatch.stop(action.payload.exitCode) + break + } + case RemoteGen.trackerChangeFollow: { + storeRegistry.getState('tracker2').dispatch.changeFollow(action.payload.guiID, action.payload.follow) + break + } + case RemoteGen.trackerIgnore: { + storeRegistry.getState('tracker2').dispatch.ignore(action.payload.guiID) + break + } + case RemoteGen.trackerCloseTracker: { + storeRegistry.getState('tracker2').dispatch.closeTracker(action.payload.guiID) + break + } + case RemoteGen.trackerLoad: { + storeRegistry.getState('tracker2').dispatch.load(action.payload) + break + } + case RemoteGen.link: + { + const {link} = action.payload + handleAppLink(link) + } + break + case RemoteGen.installerRan: + useConfigState.getState().dispatch.installerRan() + break + case RemoteGen.updateNow: + updateApp() + break + case RemoteGen.powerMonitorEvent: + useConfigState.getState().dispatch.powerMonitorEvent(action.payload.event) + break + case RemoteGen.showMain: + useConfigState.getState().dispatch.showMain() + break + case RemoteGen.dumpLogs: + ignorePromise(useConfigState.getState().dispatch.dumpLogs(action.payload.reason)) + break + case RemoteGen.remoteWindowWantsProps: + useConfigState + .getState() + .dispatch.remoteWindowNeedsProps(action.payload.component, action.payload.param) + break + case RemoteGen.updateWindowMaxState: + useConfigState.setState(s => { + s.windowState.isMaximized = action.payload.max + }) + break + case RemoteGen.updateWindowState: + useConfigState.getState().dispatch.updateWindowState(action.payload.windowState) + break + case RemoteGen.updateWindowShown: { + const win = action.payload.component + useConfigState.setState(s => { + s.windowShownCount.set(win, (s.windowShownCount.get(win) ?? 0) + 1) + }) + break + } + case RemoteGen.previewConversation: + storeRegistry + .getState('chat') + .dispatch.previewConversation({participants: [action.payload.participant], reason: 'tracker'}) + break + } +} // node side plumbs through initial pref so we avoid flashes const darkModeFromNode = window.location.search.match(/darkMode=(light|dark)/) @@ -49,16 +254,20 @@ const setupApp = () => { disableDragDrop() const {batch} = C.useWaitingState.getState().dispatch - const eng = makeEngine(batch, () => { - // do nothing we wait for the remote version from node - }) + const eng = makeEngine( + batch, + () => { + // do nothing we wait for the remote version from node + }, + onEngineIncoming + ) initPlatformListener() eng.listenersAreReady() ipcRendererOn?.('KBdispatchAction', (_: unknown, action: unknown) => { setTimeout(() => { try { - useConfigState.getState().dispatch.eventFromRemoteWindows(action as RemoteGen.Actions) + eventFromRemoteWindows(action as RemoteGen.Actions) } catch {} }, 0) }) diff --git a/shared/desktop/webpack.config.babel.js b/shared/desktop/webpack.config.babel.js index 061ead3748fc..b14d20a5fbf0 100644 --- a/shared/desktop/webpack.config.babel.js +++ b/shared/desktop/webpack.config.babel.js @@ -8,22 +8,19 @@ import path from 'path' import webpack from 'webpack' import HtmlWebpackPlugin from 'html-webpack-plugin' import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin' -import CircularDependencyPlugin from 'circular-dependency-plugin' const ignoredModules = require('../ignored-modules') const enableWDYR = require('../util/why-did-you-render-enabled') const elecVersion = require('../package.json').devDependencies.electron // true if you want to debug unused code. This makes single chunks so you can grep for 'unused harmony' in the output in desktop/dist const debugUnusedChunks = false -const enableCircularDepCheck = false const evalDevtools = false -if (enableWDYR || debugUnusedChunks || enableCircularDepCheck || evalDevtools) { +if (enableWDYR || debugUnusedChunks || evalDevtools) { for (let i = 0; i < 10; ++i) { console.error('Webpack debugging on!!!', { enableWDYR, debugUnusedChunks, - enableCircularDepCheck, evalDevtools, }) } @@ -188,23 +185,6 @@ const config = (_, {mode}) => { new webpack.DefinePlugin(defines), // Inject some defines new webpack.IgnorePlugin({resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/}), // Skip a bunch of crap moment pulls in ...(enableWDYR ? [] : [new webpack.IgnorePlugin({resourceRegExp: /^lodash$/})]), // Disallow entire lodash, but needed by why did - ...(isDev && enableCircularDepCheck - ? [ - new CircularDependencyPlugin({ - // exclude detection of files based on a RegExp - exclude: /node_modules/, - // include specific files based on a RegExp - // include: /dir/, - // add errors to webpack instead of warnings - failOnError: true, - // allow import cycles that include an asyncronous import, - // e.g. via import(/* webpackMode: "weak" */ './file.js') - allowAsyncCycles: false, - // set the current working directory for displaying module paths - cwd: process.cwd(), - }), - ] - : []), ], resolve: { alias, diff --git a/shared/devices/add-device.tsx b/shared/devices/add-device.tsx index 3b5726f11013..e27c6efffcc5 100644 --- a/shared/devices/add-device.tsx +++ b/shared/devices/add-device.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as React from 'react' import * as Kb from '@/common-adapters' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' type OwnProps = { highlight?: Array<'computer' | 'phone' | 'paper key'> diff --git a/shared/devices/device-icon.tsx b/shared/devices/device-icon.tsx index 0121619b3fd5..cc698e7fe61d 100644 --- a/shared/devices/device-icon.tsx +++ b/shared/devices/device-icon.tsx @@ -1,5 +1,5 @@ -import type * as Provision from '@/constants/provision' -import * as Devices from '@/constants/devices' +import type * as Provision from '@/stores/provision' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import type {IconStyle} from '@/common-adapters/icon' diff --git a/shared/devices/device-page.tsx b/shared/devices/device-page.tsx index dff22308c890..f270c62a1b27 100644 --- a/shared/devices/device-page.tsx +++ b/shared/devices/device-page.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' diff --git a/shared/devices/device-revoke.tsx b/shared/devices/device-revoke.tsx index 374bc6d13354..90e0850a35fe 100644 --- a/shared/devices/device-revoke.tsx +++ b/shared/devices/device-revoke.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import {useConfigState} from '@/constants/config' -import * as Devices from '@/constants/devices' +import {useConfigState} from '@/stores/config' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' -import {settingsDevicesTab} from '@/constants/settings' -import {useCurrentUserState} from '@/constants/current-user' +import {settingsDevicesTab} from '@/stores/settings' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {deviceID: string} @@ -97,7 +97,7 @@ const useRevoke = (deviceID = '') => { try { await T.RPCGen.loginDeprovisionRpcPromise({doRevoke: true, username}, C.waitingKeyDevices) load() - useConfigState.getState().dispatch.revoke(deviceName) + useConfigState.getState().dispatch.revoke(deviceName, wasCurrentDevice) } catch {} } else { try { @@ -106,7 +106,7 @@ const useRevoke = (deviceID = '') => { C.waitingKeyDevices ) load() - useConfigState.getState().dispatch.revoke(deviceName) + useConfigState.getState().dispatch.revoke(deviceName, wasCurrentDevice) navUpToScreen( C.isMobile ? (C.isTablet ? C.Tabs.settingsTab : settingsDevicesTab) : C.Tabs.devicesTab ) diff --git a/shared/devices/index.tsx b/shared/devices/index.tsx index ebef2b008e50..4df196248347 100644 --- a/shared/devices/index.tsx +++ b/shared/devices/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import DeviceRow, {NewContext} from './row' diff --git a/shared/devices/nav-header.tsx b/shared/devices/nav-header.tsx index 2176976bb9c3..66506b11fbe8 100644 --- a/shared/devices/nav-header.tsx +++ b/shared/devices/nav-header.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import type * as DevicesType from '@/constants/devices' +import type * as DevicesType from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' export const HeaderTitle = () => { - const Devices = require('@/constants/devices') as typeof DevicesType + const Devices = require('@/stores/devices') as typeof DevicesType const numActive = Devices.useActiveDeviceCounts() const numRevoked = Devices.useRevokedDeviceCounts() return ( diff --git a/shared/devices/row.tsx b/shared/devices/row.tsx index 5a384bbb9d81..3817ce3ef759 100644 --- a/shared/devices/row.tsx +++ b/shared/devices/row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import DeviceIcon from './device-icon' diff --git a/shared/engine/index-impl.tsx b/shared/engine/index-impl.tsx index 14f389095d1d..14513d9c5fa5 100644 --- a/shared/engine/index-impl.tsx +++ b/shared/engine/index-impl.tsx @@ -12,7 +12,6 @@ import {printOutstandingRPCs} from '@/local-debug' import {resetClient, createClient, rpcLog, type CreateClientType, type PayloadType} from './index.platform' import {type RPCError, convertToError} from '@/util/errors' import type * as EngineGen from '../actions/engine-gen-gen' -import type * as EngineConst from '@/constants/engine' // delay incoming to stop react from queueing too many setState calls and stopping rendering // only while debugging for now @@ -47,6 +46,8 @@ class Engine { _listenersAreReady: boolean = false _emitWaiting: (changes: BatchParams) => void + _incomingTimeout: NodeJS.Timeout | undefined + _onEngineIncoming?: (action: EngineGen.Actions) => void _queuedChanges: Array<{error?: RPCError; increment: boolean; key: WaitingKey}> = [] dispatchWaitingAction = (key: WaitingKey, waiting: boolean, error?: RPCError) => { @@ -63,13 +64,18 @@ class Engine { constructor( emitWaiting: (changes: BatchParams) => void, onConnected: (c: boolean) => void, - allowIncomingCalls = true + onEngineIncoming?: (action: EngineGen.Actions) => void ) { this._onConnectedCB = onConnected + this._onEngineIncoming = onEngineIncoming // the node engine doesn't do this and we don't want to pull in any reqs - if (allowIncomingCalls) { - const {useEngineState} = require('@/constants/engine') as typeof EngineConst - this._engineConstantsIncomingCall = useEngineState.getState().dispatch.onEngineIncoming + if (onEngineIncoming) { + this._engineConstantsIncomingCall = (action: EngineGen.Actions) => { + // defer a frame so its more like before + this._incomingTimeout = setTimeout(() => { + this._onEngineIncoming?.(action) + }, 0) + } } this._emitWaiting = emitWaiting this._rpcClient = createClient( @@ -282,14 +288,14 @@ if (__DEV__) { const makeEngine = ( emitWaiting: (b: BatchParams) => void, onConnected: (c: boolean) => void, - allowIncomingCalls = true + onEngineIncoming?: (action: EngineGen.Actions) => void ) => { if (__DEV__ && engine) { logger.warn('makeEngine called multiple times') } if (!engine) { - engine = new Engine(emitWaiting, onConnected, allowIncomingCalls) + engine = new Engine(emitWaiting, onConnected, onEngineIncoming) initEngine(engine) initEngineListener(engineListener) } diff --git a/shared/engine/index.d.ts b/shared/engine/index.d.ts index cd74ba25e572..e352925abb33 100644 --- a/shared/engine/index.d.ts +++ b/shared/engine/index.d.ts @@ -29,7 +29,7 @@ export declare function getEngine(): Engine export declare function makeEngine( emitWaiting: (b: BatchParams) => void, onConnected: (c: boolean) => void, - allowIncomingCalls?: boolean + onEngineIncoming?: (action: EngineGen.Actions) => void ): Engine export default getEngine export type {IncomingCallMapType, CustomResponseIncomingCallMapType} diff --git a/shared/fs/banner/conflict-banner.tsx b/shared/fs/banner/conflict-banner.tsx index 09898a2b91e6..c43e0333121f 100644 --- a/shared/fs/banner/conflict-banner.tsx +++ b/shared/fs/banner/conflict-banner.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import openUrl from '@/util/open-url' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { path: T.FS.Path @@ -33,7 +33,7 @@ const ConnectedBanner = (ownProps: OwnProps) => { }, [startManualConflictResolution, path]) const openPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openPathInSystemFileManagerDesktop + s => s.dispatch.defer.openPathInSystemFileManagerDesktop ) const openInSystemFileManager = React.useCallback( diff --git a/shared/fs/banner/public-reminder.tsx b/shared/fs/banner/public-reminder.tsx index 16783c602a96..717e78175cf4 100644 --- a/shared/fs/banner/public-reminder.tsx +++ b/shared/fs/banner/public-reminder.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/banner/reset-banner.tsx b/shared/fs/banner/reset-banner.tsx index 91e09bfd1b13..3606d16b8f2c 100644 --- a/shared/fs/banner/reset-banner.tsx +++ b/shared/fs/banner/reset-banner.tsx @@ -4,10 +4,10 @@ import * as T from '@/constants/types' import {folderNameWithoutUsers} from '@/util/kbfs' import * as Kb from '@/common-adapters' import * as RowTypes from '@/fs/browser/rows/types' -import {useTrackerState} from '@/constants/tracker2' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useProfileState} from '@/stores/profile' type OwnProps = {path: T.FS.Path} @@ -21,7 +21,7 @@ const ConnectedBanner = (ownProps: OwnProps) => { if (pathElems.length < 3) return const filteredPathName = folderNameWithoutUsers(pathElems[2] ?? '', users) const filteredPath = T.FS.stringToPath(['', pathElems[0], pathElems[1], filteredPathName].join('/')) - FS.makeActionForOpenPathInFilesTab(filteredPath) + FS.navToPath(filteredPath) }, [] ) diff --git a/shared/fs/banner/system-file-manager-integration-banner/container.tsx b/shared/fs/banner/system-file-manager-integration-banner/container.tsx index 02877de920b0..232e32b74c5d 100644 --- a/shared/fs/banner/system-file-manager-integration-banner/container.tsx +++ b/shared/fs/banner/system-file-manager-integration-banner/container.tsx @@ -3,8 +3,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as Kbfs from '@/fs/common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = {alwaysShow?: boolean} @@ -14,7 +14,7 @@ const SFMIContainer = (op: OwnProps) => { driverDisable: s.dispatch.driverDisable, driverEnable: s.dispatch.driverEnable, driverStatus: s.sfmi.driverStatus, - setSfmiBannerDismissedDesktop: s.dispatch.dynamic.setSfmiBannerDismissedDesktop, + setSfmiBannerDismissedDesktop: s.dispatch.defer.setSfmiBannerDismissedDesktop, settings: s.settings, })) ) @@ -218,7 +218,7 @@ const JustEnabled = ({onDismiss}: JustEnabledProps) => { const {displayingMountDir, openLocalPathInSystemFileManagerDesktop} = useFSState( C.useShallow(s => ({ displayingMountDir: s.sfmi.preferredMountDirs[0] ?? '', - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, })) ) const open = displayingMountDir diff --git a/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx b/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx index 759aa1d2fe21..c294c7b776ea 100644 --- a/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx +++ b/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx @@ -2,11 +2,11 @@ import * as React from 'react' import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const InstallSecurityPrefs = () => { const driverStatus = useFSState(s => s.sfmi.driverStatus) - const openSecurityPreferencesDesktop = useFSState(s => s.dispatch.dynamic.openSecurityPreferencesDesktop) + const openSecurityPreferencesDesktop = useFSState(s => s.dispatch.defer.openSecurityPreferencesDesktop) const onCancel = C.useRouterState(s => s.dispatch.navigateUp) const openSecurityPrefs = React.useCallback( () => openSecurityPreferencesDesktop?.(), diff --git a/shared/fs/browser/destination-picker.tsx b/shared/fs/browser/destination-picker.tsx index 9a407bb950b8..b00bdd56608b 100644 --- a/shared/fs/browser/destination-picker.tsx +++ b/shared/fs/browser/destination-picker.tsx @@ -8,8 +8,8 @@ import NavHeaderTitle from '@/fs/nav-header/title' import Root from './root' import Rows from './rows/rows-container' import {OriginalOrCompressedButton} from '@/incoming-share' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = {index: number} diff --git a/shared/fs/browser/index.tsx b/shared/fs/browser/index.tsx index c4e8df63334d..29410ae9c1bc 100644 --- a/shared/fs/browser/index.tsx +++ b/shared/fs/browser/index.tsx @@ -10,8 +10,8 @@ import PublicReminder from '../banner/public-reminder' import Root from './root' import Rows from './rows/rows-container' import {asRows as resetBannerAsRows} from '../banner/reset-banner' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = {path: T.FS.Path} @@ -68,7 +68,7 @@ const DragAndDrop = React.memo(function DragAndDrop(p: { rejectReason?: string }) { const {children, path, rejectReason} = p - const uploadFromDragAndDrop = useFSState(s => s.dispatch.dynamic.uploadFromDragAndDropDesktop) + const uploadFromDragAndDrop = useFSState(s => s.dispatch.defer.uploadFromDragAndDropDesktop) const onAttach = React.useCallback( (localPaths: Array) => uploadFromDragAndDrop?.(path, localPaths), [path, uploadFromDragAndDrop] diff --git a/shared/fs/browser/offline.tsx b/shared/fs/browser/offline.tsx index 51f045b4fdba..3a3273e64ffe 100644 --- a/shared/fs/browser/offline.tsx +++ b/shared/fs/browser/offline.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import TopBar from '../top-bar' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/browser/root.tsx b/shared/fs/browser/root.tsx index af39fbc45ea1..1a1cef90548c 100644 --- a/shared/fs/browser/root.tsx +++ b/shared/fs/browser/root.tsx @@ -5,9 +5,9 @@ import TlfType from './rows/tlf-type' import Tlf from './rows/tlf' import SfmiBanner from '../banner/system-file-manager-integration-banner/container' import {WrapRow} from './rows/rows' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type Props = { destinationPickerIndex?: number diff --git a/shared/fs/browser/rows/editing.tsx b/shared/fs/browser/rows/editing.tsx index e6e19567d756..99ac037113a7 100644 --- a/shared/fs/browser/rows/editing.tsx +++ b/shared/fs/browser/rows/editing.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {rowStyles} from './common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { editID: T.FS.EditID diff --git a/shared/fs/browser/rows/rows-container.tsx b/shared/fs/browser/rows/rows-container.tsx index 9c3852859b96..37019066d45d 100644 --- a/shared/fs/browser/rows/rows-container.tsx +++ b/shared/fs/browser/rows/rows-container.tsx @@ -4,9 +4,9 @@ import * as RowTypes from './types' import {sortRowItems, type SortableRowItem} from './sort' import Rows, {type Props} from './rows' import {asRows as topBarAsRow} from '../../top-bar' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { path: T.FS.Path // path to the parent folder containering the rows, diff --git a/shared/fs/browser/rows/still.tsx b/shared/fs/browser/rows/still.tsx index c5b1655ff3b4..f0a2ad9abbef 100644 --- a/shared/fs/browser/rows/still.tsx +++ b/shared/fs/browser/rows/still.tsx @@ -4,8 +4,8 @@ import {useOpen} from '@/fs/common/use-open' import {rowStyles, StillCommon} from './common' import * as Kb from '@/common-adapters' import {LastModifiedLine, Filename} from '@/fs/common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { destinationPickerIndex?: number diff --git a/shared/fs/browser/rows/tlf-type.tsx b/shared/fs/browser/rows/tlf-type.tsx index 908334f54352..e60d181d6f39 100644 --- a/shared/fs/browser/rows/tlf-type.tsx +++ b/shared/fs/browser/rows/tlf-type.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import {useOpen} from '@/fs/common/use-open' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import {rowStyles, StillCommon} from './common' import * as Kb from '@/common-adapters' diff --git a/shared/fs/browser/rows/tlf.tsx b/shared/fs/browser/rows/tlf.tsx index 001ba2b64cdd..1092deddbaff 100644 --- a/shared/fs/browser/rows/tlf.tsx +++ b/shared/fs/browser/rows/tlf.tsx @@ -3,9 +3,9 @@ import {useOpen} from '@/fs/common/use-open' import {rowStyles, StillCommon} from './common' import * as Kb from '@/common-adapters' import {useFsPathMetadata, TlfInfoLine, Filename} from '@/fs/common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { destinationPickerIndex?: number diff --git a/shared/fs/common/errs-container.tsx b/shared/fs/common/errs-container.tsx index 7e86e484dd46..ae768869412a 100644 --- a/shared/fs/common/errs-container.tsx +++ b/shared/fs/common/errs-container.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const ErrsContainer = () => { const {_errors, _dismiss} = useFSState( diff --git a/shared/fs/common/folder-view-filter-icon.tsx b/shared/fs/common/folder-view-filter-icon.tsx index a843e380bf81..135aff8989f2 100644 --- a/shared/fs/common/folder-view-filter-icon.tsx +++ b/shared/fs/common/folder-view-filter-icon.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type * as Styles from '@/styles' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { onClick: () => void diff --git a/shared/fs/common/folder-view-filter.tsx b/shared/fs/common/folder-view-filter.tsx index 7cf7dc787a71..45e1c6d835da 100644 --- a/shared/fs/common/folder-view-filter.tsx +++ b/shared/fs/common/folder-view-filter.tsx @@ -3,8 +3,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import debounce from 'lodash/debounce' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { onCancel?: () => void diff --git a/shared/fs/common/hooks.tsx b/shared/fs/common/hooks.tsx index be2ac0bcaa39..bb30566beb4f 100644 --- a/shared/fs/common/hooks.tsx +++ b/shared/fs/common/hooks.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import logger from '@/logger' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' const isPathItem = (path: T.FS.Path) => T.FS.getPathLevel(path) > 2 || FS.hasSpecialFileElement(path) @@ -185,8 +185,8 @@ export const useFsWatchDownloadForMobile = C.isMobile const {dlState, finishedDownloadWithIntentMobile, finishedRegularDownloadMobile} = useFSState( C.useShallow(s => ({ dlState: s.downloads.state.get(downloadID) || FS.emptyDownloadState, - finishedDownloadWithIntentMobile: s.dispatch.dynamic.finishedDownloadWithIntentMobile, - finishedRegularDownloadMobile: s.dispatch.dynamic.finishedRegularDownloadMobile, + finishedDownloadWithIntentMobile: s.dispatch.defer.finishedDownloadWithIntentMobile, + finishedRegularDownloadMobile: s.dispatch.defer.finishedRegularDownloadMobile, })) ) const finished = dlState !== FS.emptyDownloadState && !FS.downloadIsOngoing(dlState) diff --git a/shared/fs/common/item-icon.tsx b/shared/fs/common/item-icon.tsx index 303b8e533dc2..8b0d42789caf 100644 --- a/shared/fs/common/item-icon.tsx +++ b/shared/fs/common/item-icon.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {IconType} from '@/common-adapters/icon' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type Size = 96 | 48 | 32 | 16 type SizeString = '96' | '48' | '32' | '16' diff --git a/shared/fs/common/kbfs-path.tsx b/shared/fs/common/kbfs-path.tsx index bc000fb40d44..c49cd7cd96e7 100644 --- a/shared/fs/common/kbfs-path.tsx +++ b/shared/fs/common/kbfs-path.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import PathInfo from './path-info' import PathItemInfo from './path-item-info' @@ -18,7 +18,7 @@ type PopupProps = Props & { } const useOpenInFilesTab = (path: T.FS.Path) => { - return React.useCallback(() => FS.makeActionForOpenPathInFilesTab(path), [path]) + return React.useCallback(() => FS.navToPath(path), [path]) } const KbfsPathPopup = (props: PopupProps) => { diff --git a/shared/fs/common/last-modified-line.tsx b/shared/fs/common/last-modified-line.tsx index 7a8d7893f508..a11c6be3625f 100644 --- a/shared/fs/common/last-modified-line.tsx +++ b/shared/fs/common/last-modified-line.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {formatTimeForFS} from '@/util/timestamp' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type OwnProps = { path: T.FS.Path diff --git a/shared/fs/common/open-in-system-file-manager.tsx b/shared/fs/common/open-in-system-file-manager.tsx index 42758a5a7420..44daf606fd2e 100644 --- a/shared/fs/common/open-in-system-file-manager.tsx +++ b/shared/fs/common/open-in-system-file-manager.tsx @@ -3,13 +3,13 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as C from '@/constants' import SystemFileManagerIntegrationPopup from './sfmi-popup' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = {path: T.FS.Path} const OpenInSystemFileManager = React.memo(function OpenInSystemFileManager({path}: Props) { const openPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openPathInSystemFileManagerDesktop + s => s.dispatch.defer.openPathInSystemFileManagerDesktop ) const openInSystemFileManager = React.useCallback( () => openPathInSystemFileManagerDesktop?.(path), diff --git a/shared/fs/common/path-info.tsx b/shared/fs/common/path-info.tsx index 16e0f1ef9d51..25182ab0625a 100644 --- a/shared/fs/common/path-info.tsx +++ b/shared/fs/common/path-info.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import {useFsPathInfo} from './hooks' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type PathInfoProps = { containerStyle?: Kb.Styles.StylesCrossPlatform diff --git a/shared/fs/common/path-item-action/choose-view.tsx b/shared/fs/common/path-item-action/choose-view.tsx index 7029ddf85d26..23421a3c10cd 100644 --- a/shared/fs/common/path-item-action/choose-view.tsx +++ b/shared/fs/common/path-item-action/choose-view.tsx @@ -2,7 +2,7 @@ import * as T from '@/constants/types' import type {FloatingMenuProps} from './types' import Menu from './menu-container' import Confirm from './confirm' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { floatingMenuProps: FloatingMenuProps diff --git a/shared/fs/common/path-item-action/confirm-delete.tsx b/shared/fs/common/path-item-action/confirm-delete.tsx index fbc3d3afe1aa..d47af5f53184 100644 --- a/shared/fs/common/path-item-action/confirm-delete.tsx +++ b/shared/fs/common/path-item-action/confirm-delete.tsx @@ -2,8 +2,8 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as C from '@/constants' import * as React from 'react' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type Props = { onBack: () => void diff --git a/shared/fs/common/path-item-action/confirm.tsx b/shared/fs/common/path-item-action/confirm.tsx index 73e19306d67c..c3fb1a363ea6 100644 --- a/shared/fs/common/path-item-action/confirm.tsx +++ b/shared/fs/common/path-item-action/confirm.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as T from '@/constants/types' import type {FloatingMenuProps} from './types' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { floatingMenuProps: FloatingMenuProps diff --git a/shared/fs/common/path-item-action/index.tsx b/shared/fs/common/path-item-action/index.tsx index 46087ca280e2..00c24879ddbb 100644 --- a/shared/fs/common/path-item-action/index.tsx +++ b/shared/fs/common/path-item-action/index.tsx @@ -4,8 +4,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import ChooseView from './choose-view' import type {SizeType} from '@/common-adapters/icon' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type ClickableProps = { onClick: () => void diff --git a/shared/fs/common/path-item-action/layout.tsx b/shared/fs/common/path-item-action/layout.tsx index ec63a3a52b64..89a91060c446 100644 --- a/shared/fs/common/path-item-action/layout.tsx +++ b/shared/fs/common/path-item-action/layout.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' export type Layout = { archive: boolean diff --git a/shared/fs/common/path-item-action/menu-container.tsx b/shared/fs/common/path-item-action/menu-container.tsx index 996f366ea73c..74d8f8c7180b 100644 --- a/shared/fs/common/path-item-action/menu-container.tsx +++ b/shared/fs/common/path-item-action/menu-container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as Kbfs from '@/fs/common/hooks' import * as React from 'react' @@ -8,9 +8,9 @@ import * as Util from '@/util/kbfs' import Header from './header' import type {FloatingMenuProps} from './types' import {getRootLayout, getShareLayout} from './layout' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { floatingMenuProps: FloatingMenuProps @@ -32,7 +32,7 @@ const Container = (op: OwnProps) => { const fileContext = s.fileContext.get(path) || FS.emptyFileContext const {cancelDownload, setPathItemActionMenuView, download, newFolderRow} = s.dispatch const {favoriteIgnore, startRename, dismissDownload} = s.dispatch - const {openPathInSystemFileManagerDesktop} = s.dispatch.dynamic + const {openPathInSystemFileManagerDesktop} = s.dispatch.defer const sfmiEnabled = s.sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled return { cancelDownload, diff --git a/shared/fs/common/path-item-info.tsx b/shared/fs/common/path-item-info.tsx index 75d4bc69fb06..bd9d22d388f1 100644 --- a/shared/fs/common/path-item-info.tsx +++ b/shared/fs/common/path-item-info.tsx @@ -6,8 +6,8 @@ import ItemIcon from './item-icon' import CommaSeparatedName from './comma-separated-name' import {pluralize} from '@/util/string' import {useFsChildren, useFsPathMetadata, useFsOnlineStatus, useFsSoftError} from './hooks' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { containerStyle?: Kb.Styles.StylesCrossPlatform diff --git a/shared/fs/common/path-status-icon-container.tsx b/shared/fs/common/path-status-icon-container.tsx index 9a1440a507e2..0e6cb624b358 100644 --- a/shared/fs/common/path-status-icon-container.tsx +++ b/shared/fs/common/path-status-icon-container.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import PathStatusIcon from './path-status-icon' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnPropsPathItem = { path: T.FS.Path diff --git a/shared/fs/common/refresh-driver-status-on-mount.tsx b/shared/fs/common/refresh-driver-status-on-mount.tsx index 9ce3cc4d12d5..a710da6894c8 100644 --- a/shared/fs/common/refresh-driver-status-on-mount.tsx +++ b/shared/fs/common/refresh-driver-status-on-mount.tsx @@ -1,8 +1,8 @@ import * as React from 'react' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const RefreshDriverStatusOnMount = () => { - const refreshDriverStatusDesktop = useFSState(s => s.dispatch.dynamic.refreshDriverStatusDesktop) + const refreshDriverStatusDesktop = useFSState(s => s.dispatch.defer.refreshDriverStatusDesktop) const refresh = React.useCallback(() => refreshDriverStatusDesktop?.(), [refreshDriverStatusDesktop]) React.useEffect(() => { diff --git a/shared/fs/common/sfmi-popup.tsx b/shared/fs/common/sfmi-popup.tsx index 9fbf8764643b..33aa3bbe952b 100644 --- a/shared/fs/common/sfmi-popup.tsx +++ b/shared/fs/common/sfmi-popup.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {useFuseClosedSourceConsent} from './hooks' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = { mode: 'Icon' | 'Button' diff --git a/shared/fs/common/tlf-info-line-container.tsx b/shared/fs/common/tlf-info-line-container.tsx index 8b84d8962edd..09803b935e53 100644 --- a/shared/fs/common/tlf-info-line-container.tsx +++ b/shared/fs/common/tlf-info-line-container.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import TlfInfoLine from './tlf-info-line' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { path: T.FS.Path diff --git a/shared/fs/common/upload-button.tsx b/shared/fs/common/upload-button.tsx index 593688df6905..3021fbfbfa05 100644 --- a/shared/fs/common/upload-button.tsx +++ b/shared/fs/common/upload-button.tsx @@ -3,8 +3,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import * as Kb from '@/common-adapters' import type * as Styles from '@/styles' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { path: T.FS.Path @@ -73,8 +73,8 @@ const UploadButton = (props: UploadButtonProps) => { const Container = (ownProps: OwnProps) => { const _pathItem = useFSState(s => FS.getPathItem(s.pathItems, ownProps.path)) - const openAndUploadDesktop = useFSState(s => s.dispatch.dynamic.openAndUploadDesktop) - const pickAndUploadMobile = useFSState(s => s.dispatch.dynamic.pickAndUploadMobile) + const openAndUploadDesktop = useFSState(s => s.dispatch.defer.openAndUploadDesktop) + const pickAndUploadMobile = useFSState(s => s.dispatch.defer.pickAndUploadMobile) const _openAndUploadBoth = () => { openAndUploadDesktop?.(T.FS.OpenDialogType.Both, ownProps.path) } diff --git a/shared/fs/common/use-open.tsx b/shared/fs/common/use-open.tsx index 0e15b1719686..74a9ea2be194 100644 --- a/shared/fs/common/use-open.tsx +++ b/shared/fs/common/use-open.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/filepreview/bare-preview.tsx b/shared/fs/filepreview/bare-preview.tsx index bcbe04f750c7..f9d95b402178 100644 --- a/shared/fs/filepreview/bare-preview.tsx +++ b/shared/fs/filepreview/bare-preview.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import Footer from '../footer/footer' import View from './view' import * as Kbfs from '../common' diff --git a/shared/fs/filepreview/default-view.tsx b/shared/fs/filepreview/default-view.tsx index 41d252be7d54..3fa036521c01 100644 --- a/shared/fs/filepreview/default-view.tsx +++ b/shared/fs/filepreview/default-view.tsx @@ -3,8 +3,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import {PathItemAction, LastModifiedLine, ItemIcon, type ClickableProps} from '../common' import {hasShare} from '../common/path-item-action/layout' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type OwnProps = {path: T.FS.Path} @@ -19,7 +19,7 @@ const Container = (ownProps: OwnProps) => { C.useShallow(s => ({ _download: s.dispatch.download, fileContext: s.fileContext.get(path) || FS.emptyFileContext, - openPathInSystemFileManagerDesktop: s.dispatch.dynamic.openPathInSystemFileManagerDesktop, + openPathInSystemFileManagerDesktop: s.dispatch.defer.openPathInSystemFileManagerDesktop, pathItem: FS.getPathItem(s.pathItems, path), sfmiEnabled: s.sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled, })) diff --git a/shared/fs/filepreview/view.tsx b/shared/fs/filepreview/view.tsx index 2765510266e7..aad5f4757683 100644 --- a/shared/fs/filepreview/view.tsx +++ b/shared/fs/filepreview/view.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as T from '@/constants/types' import DefaultView from './default-view' @@ -7,8 +7,8 @@ import TextView from './text-view' import AVView from './av-view' import PdfView from './pdf-view' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/footer/download.tsx b/shared/fs/footer/download.tsx index 3d2177cbd799..9cf91a45cefb 100644 --- a/shared/fs/footer/download.tsx +++ b/shared/fs/footer/download.tsx @@ -4,8 +4,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import DownloadWrapper from './download-wrapper' import {formatDurationFromNowTo} from '@/util/timestamp' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' export type Props = { downloadID: string @@ -37,7 +37,7 @@ const Download = (props: Props) => { cancelDownload: s.dispatch.cancelDownload, dismissDownload: s.dispatch.dismissDownload, dlState: s.downloads.state.get(props.downloadID) || FS.emptyDownloadState, - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, })) ) const open = dlState.localPath diff --git a/shared/fs/footer/downloads.tsx b/shared/fs/footer/downloads.tsx index 7983b96c0020..f00942abd8b0 100644 --- a/shared/fs/footer/downloads.tsx +++ b/shared/fs/footer/downloads.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' import * as Kbfs from '../common' import Download from './download' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const Mobile = () => { Kbfs.useFsDownloadStatus() @@ -33,7 +33,7 @@ const Desktop = () => { const {downloadIDs, openLocalPathInSystemFileManagerDesktop} = useFSState( C.useShallow(s => ({ downloadIDs: s.downloads.regularDownloads, - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, })) ) const openDownloadFolder = () => openLocalPathInSystemFileManagerDesktop?.(C.downloadFolder) diff --git a/shared/fs/footer/proof-broken.tsx b/shared/fs/footer/proof-broken.tsx index 186b5106bb28..852d5b5f925d 100644 --- a/shared/fs/footer/proof-broken.tsx +++ b/shared/fs/footer/proof-broken.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import * as FS from '@/constants/fs' -import {useUsersState} from '@/constants/users' +import * as FS from '@/stores/fs' +import {useUsersState} from '@/stores/users' type Props = {path: T.FS.Path} diff --git a/shared/fs/footer/upload-container.tsx b/shared/fs/footer/upload-container.tsx index 73bfd3b2d3ef..31193f51a235 100644 --- a/shared/fs/footer/upload-container.tsx +++ b/shared/fs/footer/upload-container.tsx @@ -2,8 +2,8 @@ import * as T from '@/constants/types' import Upload from './upload' import {useUploadCountdown} from './use-upload-countdown' import * as C from '@/constants' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' // NOTE flip this to show a button to debug the upload banner animations. const enableDebugUploadBanner = false as boolean diff --git a/shared/fs/index.tsx b/shared/fs/index.tsx index 304ce8342ced..78fa22597830 100644 --- a/shared/fs/index.tsx +++ b/shared/fs/index.tsx @@ -5,8 +5,8 @@ import Browser from './browser' import {NormalPreview} from './filepreview' import * as Kbfs from './common' import * as SimpleScreens from './simple-screens' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type ChooseComponentProps = { emitBarePreview: () => void diff --git a/shared/fs/nav-header/actions.tsx b/shared/fs/nav-header/actions.tsx index 497c67488b7f..014b0c7e1331 100644 --- a/shared/fs/nav-header/actions.tsx +++ b/shared/fs/nav-header/actions.tsx @@ -3,8 +3,8 @@ import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' import * as Kbfs from '../common' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = { onTriggerFilterMobile: () => void diff --git a/shared/fs/nav-header/main-banner.tsx b/shared/fs/nav-header/main-banner.tsx index 73150635e474..4c24a1390ca4 100644 --- a/shared/fs/nav-header/main-banner.tsx +++ b/shared/fs/nav-header/main-banner.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type Props = { onRetry: () => void diff --git a/shared/fs/nav-header/mobile-header.tsx b/shared/fs/nav-header/mobile-header.tsx index 1364d912bc1c..3868d0e25ba9 100644 --- a/shared/fs/nav-header/mobile-header.tsx +++ b/shared/fs/nav-header/mobile-header.tsx @@ -5,8 +5,8 @@ import * as Kbfs from '../common' import type * as T from '@/constants/types' import Actions from './actions' import MainBanner from './main-banner' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' /* * diff --git a/shared/fs/nav-header/title.tsx b/shared/fs/nav-header/title.tsx index a1419632345f..1bcb3e156a77 100644 --- a/shared/fs/nav-header/title.tsx +++ b/shared/fs/nav-header/title.tsx @@ -3,7 +3,7 @@ import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as Kbfs from '../common' import {useSafeNavigation} from '@/util/safe-navigation' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/routes.tsx b/shared/fs/routes.tsx index f96c5d4f17c3..f9d6796ef092 100644 --- a/shared/fs/routes.tsx +++ b/shared/fs/routes.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as T from '@/constants/types' import * as C from '@/constants' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import {Actions, MainBanner, MobileHeader, Title} from './nav-header' const FsRoot = React.lazy(async () => import('.')) diff --git a/shared/fs/top-bar/loading.tsx b/shared/fs/top-bar/loading.tsx index 1aee292386c8..214ecb0a9539 100644 --- a/shared/fs/top-bar/loading.tsx +++ b/shared/fs/top-bar/loading.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' // The behavior is to only show spinner when user first time lands on a screen // and when don't have the data that drives it yet. Since RPCs happen diff --git a/shared/fs/top-bar/sort.tsx b/shared/fs/top-bar/sort.tsx index f2057895d10f..30db41b3f6be 100644 --- a/shared/fs/top-bar/sort.tsx +++ b/shared/fs/top-bar/sort.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { path: T.FS.Path diff --git a/shared/fs/top-bar/sync-toggle.tsx b/shared/fs/top-bar/sync-toggle.tsx index 437300313c8d..cdc1e573a46d 100644 --- a/shared/fs/top-bar/sync-toggle.tsx +++ b/shared/fs/top-bar/sync-toggle.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { tlfPath: T.FS.Path diff --git a/shared/git/delete-repo.tsx b/shared/git/delete-repo.tsx index 88b1804a1f83..1be08287171f 100644 --- a/shared/git/delete-repo.tsx +++ b/shared/git/delete-repo.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' -import * as Git from '@/constants/git' +import * as Git from '@/stores/git' type OwnProps = {id: string} diff --git a/shared/git/index.tsx b/shared/git/index.tsx index 168a9715ed72..aaf29607752f 100644 --- a/shared/git/index.tsx +++ b/shared/git/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' +import * as Git from '@/stores/git' import * as Kb from '@/common-adapters' import * as React from 'react' import Row, {NewContext} from './row' diff --git a/shared/git/nav-header.tsx b/shared/git/nav-header.tsx index 5dafe65dd6ca..26d239bb94c9 100644 --- a/shared/git/nav-header.tsx +++ b/shared/git/nav-header.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' +import * as Git from '@/stores/git' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/git/new-repo.tsx b/shared/git/new-repo.tsx index fd7ded7e79f5..2672731dc6a7 100644 --- a/shared/git/new-repo.tsx +++ b/shared/git/new-repo.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' -import * as Teams from '@/constants/teams' +import * as Git from '@/stores/git' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/git/row.tsx b/shared/git/row.tsx index 4402b545eb79..af0167090a53 100644 --- a/shared/git/row.tsx +++ b/shared/git/row.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' -import * as Teams from '@/constants/teams' +import * as Git from '@/stores/git' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' import openURL from '@/util/open-url' -import {useTrackerState} from '@/constants/tracker2' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' export const NewContext = React.createContext>(new Set()) @@ -28,7 +28,7 @@ const ConnectedRow = React.memo(function ConnectedRow(ownProps: OwnProps) { const isNew = React.useContext(NewContext).has(id) const you = useCurrentUserState(s => s.username) const setTeamRepoSettings = Git.useGitState(s => s.dispatch.setTeamRepoSettings) - const _onBrowseGitRepo = FS.makeActionForOpenPathInFilesTab + const _onBrowseGitRepo = FS.navToPath const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) const {url: gitURL, repoID, channelName, teamname, chatDisabled} = git diff --git a/shared/git/select-channel.tsx b/shared/git/select-channel.tsx index a2cee7e5f3cb..a4b40c3af1af 100644 --- a/shared/git/select-channel.tsx +++ b/shared/git/select-channel.tsx @@ -1,5 +1,5 @@ -import * as Git from '@/constants/git' -import * as Teams from '@/constants/teams' +import * as Git from '@/stores/git' +import * as Teams from '@/stores/teams' import {useSafeNavigation} from '@/util/safe-navigation' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/incoming-share/index.tsx b/shared/incoming-share/index.tsx index 44c54b0619ab..3d4e7f363752 100644 --- a/shared/incoming-share/index.tsx +++ b/shared/incoming-share/index.tsx @@ -5,11 +5,11 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as FsCommon from '@/fs/common' import {MobileSendToChat} from '../chat/send-to-chat' -import {navigateAppend} from '@/constants/router2/util' -import {settingsFeedbackTab} from '@/constants/settings' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' -import {useConfigState} from '@/constants/config' +import {settingsFeedbackTab} from '@/stores/settings' +import * as FS from '@/stores/fs' +import {useConfigState} from '@/stores/config' +import {useFSState} from '@/stores/fs' +import {useRouterState} from '@/stores/router2' export const OriginalOrCompressedButton = ({incomingShareItems}: IncomingShareProps) => { const originalTotalSize = incomingShareItems.reduce((bytes, item) => bytes + (item.originalSize ?? 0), 0) @@ -204,6 +204,7 @@ type IncomingShareWithSelectionProps = IncomingShareProps & { } const IncomingShare = (props: IncomingShareWithSelectionProps) => { + const navigateAppend = useRouterState(s => s.dispatch.navigateAppend) const useOriginalValue = useConfigState(s => s.incomingShareUseOriginal) const {sendPaths, text} = props.incomingShareItems.reduce( ({sendPaths, text}, item) => { diff --git a/shared/ios/Keybase/Info.plist b/shared/ios/Keybase/Info.plist index fb65c31b600d..fc0bc7c9f8f2 100644 --- a/shared/ios/Keybase/Info.plist +++ b/shared/ios/Keybase/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 6.6.0 + 6.7.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/shared/ios/KeybaseShare/Info.plist b/shared/ios/KeybaseShare/Info.plist index d7a771229ac6..b699e9a1d1b1 100644 --- a/shared/ios/KeybaseShare/Info.plist +++ b/shared/ios/KeybaseShare/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 6.6.0 + 6.7.0 CFBundleVersion 200 NSExtension diff --git a/shared/ios/Podfile.lock b/shared/ios/Podfile.lock index 6c16b2c5df90..34be5b28b06c 100644 --- a/shared/ios/Podfile.lock +++ b/shared/ios/Podfile.lock @@ -9,7 +9,7 @@ PODS: - EXImageLoader (6.0.0): - ExpoModulesCore - React-Core - - Expo (54.0.31): + - Expo (54.0.33): - boost - DoubleConversion - ExpoModulesCore @@ -52,7 +52,7 @@ PODS: - ExpoModulesCore - ExpoFileSystem (19.0.21): - ExpoModulesCore - - ExpoFont (14.0.10): + - ExpoFont (14.0.11): - ExpoModulesCore - ExpoHaptics (15.0.8): - ExpoModulesCore @@ -1932,7 +1932,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-keyboard-controller (1.20.6): + - react-native-keyboard-controller (1.20.7): - boost - DoubleConversion - fast_float @@ -1950,7 +1950,7 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-keyboard-controller/common (= 1.20.6) + - react-native-keyboard-controller/common (= 1.20.7) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1961,7 +1961,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-keyboard-controller/common (1.20.6): + - react-native-keyboard-controller/common (1.20.7): - boost - DoubleConversion - fast_float @@ -2814,7 +2814,7 @@ PODS: - RNWorklets - SocketRocket - Yoga - - RNScreens (4.18.0): + - RNScreens (4.23.0): - boost - DoubleConversion - fast_float @@ -2841,10 +2841,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.18.0) + - RNScreens/common (= 4.23.0) - SocketRocket - Yoga - - RNScreens/common (4.18.0): + - RNScreens/common (4.23.0): - boost - DoubleConversion - fast_float @@ -2873,7 +2873,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNWorklets (0.7.1): + - RNWorklets (0.7.3): - boost - DoubleConversion - fast_float @@ -2900,10 +2900,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets (= 0.7.1) + - RNWorklets/worklets (= 0.7.3) - SocketRocket - Yoga - - RNWorklets/worklets (0.7.1): + - RNWorklets/worklets (0.7.3): - boost - DoubleConversion - fast_float @@ -2930,10 +2930,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets/apple (= 0.7.1) + - RNWorklets/worklets/apple (= 0.7.3) - SocketRocket - Yoga - - RNWorklets/worklets/apple (0.7.1): + - RNWorklets/worklets/apple (0.7.3): - boost - DoubleConversion - fast_float @@ -3323,13 +3323,13 @@ SPEC CHECKSUMS: EXAV: b60fcf142fae6684d295bc28cd7cfcb3335570ea EXConstants: fce59a631a06c4151602843667f7cfe35f81e271 EXImageLoader: 189e3476581efe3ad4d1d3fb4735b7179eb26f05 - Expo: 5fe0862b0c3de267afbba769891aa65f64c2800c + Expo: ec20d60a9ba93352323b8f773c0e59bc7aa1c492 ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18 ExpoCamera: 6a326deb45ba840749652e4c15198317aa78497e ExpoClipboard: b36b287d8356887844bb08ed5c84b5979bb4dd1e ExpoContacts: 1c976e6c0b9be9b256ea038eba5c86f5707a73ce ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063 - ExpoFont: 35ac6191ed86bbf56b3ebd2d9154eda9fad5b509 + ExpoFont: f543ce20a228dd702813668b1a07b46f51878d47 ExpoHaptics: d3a6375d8dcc3a1083d003bc2298ff654fafb536 ExpoImage: 686f972bff29525733aa13357f6691dc90aa03d8 ExpoImagePicker: 1af3e4e31512d2f34c95c2a3018a3edc40aee748 @@ -3385,7 +3385,7 @@ SPEC CHECKSUMS: React-Mapbuffer: 017336879e2e0fb7537bbc08c24f34e2384c9260 React-microtasksnativemodule: 63ee6730cec233feab9cdcc0c100dc28a12e4165 react-native-kb: 078843e8c52d210aff0c50cbb4c4abe888c28ee2 - react-native-keyboard-controller: 40b005f1202d68566a2d058ddc148a3ea8d73288 + react-native-keyboard-controller: a9e423beaa20d00a4b9664b0fa37f1cdf3a59975 react-native-netinfo: 64f05e94821ee3f3adcd9b67b35c1480e5564915 react-native-safe-area-context: 0a3b034bb63a5b684dd2f5fffd3c90ef6ed41ee8 react-native-webview: cdce419e8022d0ef6f07db21890631258e7a9e6e @@ -3423,8 +3423,8 @@ SPEC CHECKSUMS: RNCPicker: 35fc66f352403cdfe99d53b541f5180482ca2bc5 RNGestureHandler: 043d32e7e7ae6bdcca06264682d5a078712903a1 RNReanimated: e79d7f42b76ba026e7dc5fb3e3f81991c590d3af - RNScreens: 98771ad898d1c0528fc8139606bbacf5a2e9d237 - RNWorklets: 416ef974c176d76634e34c0aeda83f6b67084a88 + RNScreens: 199799bdab32fa1e17ebf938b06fec52033e81e5 + RNWorklets: ff9b9ff38df9accccbbbad692b6e435fec64e24e SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c diff --git a/shared/login/index.tsx b/shared/login/index.tsx index cd680fd69a09..1c052d3559d0 100644 --- a/shared/login/index.tsx +++ b/shared/login/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import {useConfigState} from '@/constants/config' -import {useDaemonState} from '@/constants/daemon' +import {useConfigState} from '@/stores/config' +import {useDaemonState} from '@/stores/daemon' const Loading = React.lazy(async () => import('./loading')) const Relogin = React.lazy(async () => import('./relogin/container')) diff --git a/shared/login/join-or-login.tsx b/shared/login/join-or-login.tsx index 543fb0be1d2c..0e4b0c267713 100644 --- a/shared/login/join-or-login.tsx +++ b/shared/login/join-or-login.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' import * as React from 'react' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import {InfoIcon} from '@/signup/common' -import {useSignupState} from '@/constants/signup' -import {useProvisionState} from '@/constants/provision' +import {useSignupState} from '@/stores/signup' +import {useProvisionState} from '@/stores/provision' const Intro = () => { const justDeletedSelf = useConfigState(s => s.justDeletedSelf) diff --git a/shared/login/loading.tsx b/shared/login/loading.tsx index 7d48b3dec130..41237f127c88 100644 --- a/shared/login/loading.tsx +++ b/shared/login/loading.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import {useDaemonState} from '@/constants/daemon' +import {useDaemonState} from '@/stores/daemon' const SplashContainer = () => { const failedReason = useDaemonState(s => s.handshakeFailedReason) diff --git a/shared/login/recover-password/device-selector.tsx b/shared/login/recover-password/device-selector.tsx index 150fb2d21f5a..e73f81372b15 100644 --- a/shared/login/recover-password/device-selector.tsx +++ b/shared/login/recover-password/device-selector.tsx @@ -1,5 +1,5 @@ import SelectOtherDevice from '@/provision/select-other-device' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const RecoverPasswordDeviceSelector = () => { const devices = useRecoverState(s => s.devices) diff --git a/shared/login/recover-password/error-modal.tsx b/shared/login/recover-password/error-modal.tsx index efe62179c6c4..14cb7f80d1e5 100644 --- a/shared/login/recover-password/error-modal.tsx +++ b/shared/login/recover-password/error-modal.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useConfigState} from '@/constants/config' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useConfigState} from '@/stores/config' const styles = Kb.Styles.styleSheetCreate(() => ({ padding: { diff --git a/shared/login/recover-password/error.tsx b/shared/login/recover-password/error.tsx index 789cbf323102..c38307524d2e 100644 --- a/shared/login/recover-password/error.tsx +++ b/shared/login/recover-password/error.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import type {ButtonType} from '@/common-adapters/button' import {SignupScreen} from '@/signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useConfigState} from '@/constants/config' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useConfigState} from '@/stores/config' const ConnectedError = () => { const loggedIn = useConfigState(s => s.loggedIn) diff --git a/shared/login/recover-password/explain-device.tsx b/shared/login/recover-password/explain-device.tsx index 06890c97dab8..f9bd16930337 100644 --- a/shared/login/recover-password/explain-device.tsx +++ b/shared/login/recover-password/explain-device.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {ButtonType} from '@/common-adapters/button' import {SignupScreen} from '@/signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const ConnectedExplainDevice = () => { const ed = useRecoverState(s => s.explainedDevice) diff --git a/shared/login/recover-password/paper-key.tsx b/shared/login/recover-password/paper-key.tsx index 5e2a332af6d9..9d1983af7a1d 100644 --- a/shared/login/recover-password/paper-key.tsx +++ b/shared/login/recover-password/paper-key.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import type {ButtonType} from '@/common-adapters/button' import {SignupScreen} from '@/signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const PaperKey = () => { const error = useRecoverState(s => s.paperKeyError) diff --git a/shared/login/recover-password/password.tsx b/shared/login/recover-password/password.tsx index 452ca011543a..2e61426b7edc 100644 --- a/shared/login/recover-password/password.tsx +++ b/shared/login/recover-password/password.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import {UpdatePassword} from '@/settings/password' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const Password = () => { const error = useRecoverState(s => s.passwordError) diff --git a/shared/login/recover-password/prompt-reset-shared.tsx b/shared/login/recover-password/prompt-reset-shared.tsx index da223aa06338..dff66e082417 100644 --- a/shared/login/recover-password/prompt-reset-shared.tsx +++ b/shared/login/recover-password/prompt-reset-shared.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' import * as T from '@/constants/types' import {SignupScreen} from '@/signup/common' import type {ButtonType} from '@/common-adapters/button' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' export type Props = { resetPassword?: boolean diff --git a/shared/login/relogin/container.tsx b/shared/login/relogin/container.tsx index 2e80b7856bc6..ee18a0195aa3 100644 --- a/shared/login/relogin/container.tsx +++ b/shared/login/relogin/container.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' import * as React from 'react' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import Login from '.' import sortBy from 'lodash/sortBy' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useSignupState} from '@/constants/signup' -import {useProvisionState} from '@/constants/provision' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useSignupState} from '@/stores/signup' +import {useProvisionState} from '@/stores/provision' const needPasswordError = 'passphrase cannot be empty' diff --git a/shared/login/reset/confirm.tsx b/shared/login/reset/confirm.tsx index 5ce7f9b7d53e..e8a22339520d 100644 --- a/shared/login/reset/confirm.tsx +++ b/shared/login/reset/confirm.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const ConfirmReset = () => { const hasWallet = AutoReset.useAutoResetState(s => s.hasWallet) diff --git a/shared/login/reset/modal.tsx b/shared/login/reset/modal.tsx index 3aa3e2b5ef66..9e3e48bd8db4 100644 --- a/shared/login/reset/modal.tsx +++ b/shared/login/reset/modal.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {formatDurationForAutoreset} from '@/util/timestamp' diff --git a/shared/login/reset/password-enter.tsx b/shared/login/reset/password-enter.tsx index c749c5f801e6..010a4cdaf290 100644 --- a/shared/login/reset/password-enter.tsx +++ b/shared/login/reset/password-enter.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen} from '@/signup/common' diff --git a/shared/login/reset/password-known.tsx b/shared/login/reset/password-known.tsx index c606206718b4..f955e6b7d183 100644 --- a/shared/login/reset/password-known.tsx +++ b/shared/login/reset/password-known.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen} from '@/signup/common' diff --git a/shared/login/reset/waiting.tsx b/shared/login/reset/waiting.tsx index 9dea7f219397..adf2b26d7ace 100644 --- a/shared/login/reset/waiting.tsx +++ b/shared/login/reset/waiting.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import {SignupScreen} from '@/signup/common' import {addTicker, removeTicker} from '@/util/second-timer' import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import {useSafeNavigation} from '@/util/safe-navigation' import {formatDurationForAutoreset as formatDuration} from '@/util/timestamp' diff --git a/shared/login/routes.tsx b/shared/login/routes.tsx index f6d31a7081eb..06d196706d66 100644 --- a/shared/login/routes.tsx +++ b/shared/login/routes.tsx @@ -5,7 +5,7 @@ import {InfoIcon} from '@/signup/common' import {newRoutes as provisionRoutes} from '../provision/routes-sub' import {sharedNewRoutes as settingsRoutes} from '../settings/routes' import {newRoutes as signupRoutes} from './signup/routes' -import {settingsFeedbackTab} from '@/constants/settings/util' +import {settingsFeedbackTab} from '@/constants/settings' const recoverPasswordStyles = Kb.Styles.styleSheetCreate(() => ({ questionBox: Kb.Styles.padding(Kb.Styles.globalMargins.tiny, Kb.Styles.globalMargins.tiny, 0), diff --git a/shared/login/signup/error.tsx b/shared/login/signup/error.tsx index f646b1337698..04ff47e90e7b 100644 --- a/shared/login/signup/error.tsx +++ b/shared/login/signup/error.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import {Wrapper, ContinueButton} from './common' -import {useSignupState} from '@/constants/signup' +import {useSignupState} from '@/stores/signup' const ConnectedSignupError = () => { const error = useSignupState(s => s.signupError) diff --git a/shared/menubar/chat-container.desktop.tsx b/shared/menubar/chat-container.desktop.tsx index e00fdc6bc561..6bcff1bb9438 100644 --- a/shared/menubar/chat-container.desktop.tsx +++ b/shared/menubar/chat-container.desktop.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as R from '@/constants/remote' import * as RemoteGen from '../actions/remote-gen' import * as Kb from '@/common-adapters' diff --git a/shared/menubar/files-container.desktop.tsx b/shared/menubar/files-container.desktop.tsx index 773a166b515b..aea7e75c0361 100644 --- a/shared/menubar/files-container.desktop.tsx +++ b/shared/menubar/files-container.desktop.tsx @@ -1,4 +1,4 @@ -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as R from '@/constants/remote' import * as T from '@/constants/types' import * as RemoteGen from '../actions/remote-gen' @@ -6,7 +6,7 @@ import * as FsUtil from '@/util/kbfs' import * as TimestampUtil from '@/util/timestamp' import {FilesPreview} from './files.desktop' import type {DeserializeProps} from './remote-serializer.desktop' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const FilesContainer = (p: Pick) => { const {remoteTlfUpdates} = p diff --git a/shared/menubar/remote-container.desktop.tsx b/shared/menubar/remote-container.desktop.tsx index fa658dbb9793..ed5a78d8ee00 100644 --- a/shared/menubar/remote-container.desktop.tsx +++ b/shared/menubar/remote-container.desktop.tsx @@ -1,14 +1,14 @@ import * as React from 'react' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import Menubar from './index.desktop' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import type {DeserializeProps} from './remote-serializer.desktop' import {useAvatarState} from '@/common-adapters/avatar/store' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' -import {useDaemonState} from '@/constants/daemon' -import {useDarkModeState} from '@/constants/darkmode' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' const RemoteContainer = (d: DeserializeProps) => { const {avatarRefreshCounter, badgeMap, daemonHandshakeState, darkMode, diskSpaceStatus, endEstimate} = d diff --git a/shared/menubar/remote-proxy.desktop.tsx b/shared/menubar/remote-proxy.desktop.tsx index c32e0a8c65e7..4e79d95e0e4e 100644 --- a/shared/menubar/remote-proxy.desktop.tsx +++ b/shared/menubar/remote-proxy.desktop.tsx @@ -1,7 +1,7 @@ // A mirror of the remote menubar windows. import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as T from '@/constants/types' import * as React from 'react' import KB2 from '@/util/electron.desktop' @@ -10,16 +10,16 @@ import {intersect} from '@/util/set' import {mapFilterByKey} from '@/util/map' import {serialize, type ProxyProps, type RemoteTlfUpdates} from './remote-serializer.desktop' import {useAvatarState} from '@/common-adapters/avatar/store' -import type * as NotifConstants from '@/constants/notifications' +import type * as NotifConstants from '@/stores/notifications' import {useColorScheme} from 'react-native' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' -import {useFollowerState} from '@/constants/followers' -import {useUsersState} from '@/constants/users' -import {useNotifState} from '@/constants/notifications' -import {useCurrentUserState} from '@/constants/current-user' -import {useDaemonState} from '@/constants/daemon' -import {useDarkModeState} from '@/constants/darkmode' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' +import {useFollowerState} from '@/stores/followers' +import {useUsersState} from '@/stores/users' +import {useNotifState} from '@/stores/notifications' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' const {showTray} = KB2.functions diff --git a/shared/package.json b/shared/package.json index 2b6cb61ae16f..a0bdb4e9c25e 100644 --- a/shared/package.json +++ b/shared/package.json @@ -72,20 +72,20 @@ "dependencies": { "@gorhom/bottom-sheet": "5.2.8", "@gorhom/portal": "1.0.14", - "@khanacademy/simple-markdown": "2.1.4", - "@msgpack/msgpack": "3.1.2", + "@khanacademy/simple-markdown": "2.2.1", + "@msgpack/msgpack": "3.1.3", "@react-native-community/netinfo": "11.5.2", "@react-native-masked-view/masked-view": "0.3.2", "@react-native-picker/picker": "2.11.4", - "@react-navigation/bottom-tabs": "7.9.1", - "@react-navigation/core": "7.13.7", - "@react-navigation/native": "7.1.27", - "@react-navigation/native-stack": "7.9.1", + "@react-navigation/bottom-tabs": "7.13.0", + "@react-navigation/core": "7.14.0", + "@react-navigation/native": "7.1.28", + "@react-navigation/native-stack": "7.12.0", "classnames": "2.5.1", "date-fns": "4.1.0", "emoji-datasource-apple": "16.0.0", "emoji-regex": "10.6.0", - "expo": "54.0.31", + "expo": "54.0.33", "expo-av": "16.0.8", "expo-camera": "17.0.10", "expo-clipboard": "8.0.8", @@ -99,10 +99,10 @@ "expo-media-library": "18.2.1", "expo-sms": "14.0.8", "expo-task-manager": "14.0.9", - "framed-msgpack-rpc": "keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer", - "google-libphonenumber": "3.2.43", + "framed-msgpack-rpc": "keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer-iserror2", + "google-libphonenumber": "3.2.44", "iced-runtime": "1.0.4", - "immer": "11.1.3", + "immer": "11.1.4", "lodash": "4.17.23", "lottie-react-native": "7.3.5", "lottie-web": "5.13.0", @@ -115,27 +115,27 @@ "react-list": "0.8.18", "react-native": "0.81.5", "react-native-gesture-handler": "2.30.0", - "react-native-keyboard-controller": "1.20.6", + "react-native-keyboard-controller": "1.20.7", "react-native-kb": "file:../rnmodules/react-native-kb", "react-native-reanimated": "4.2.1", - "react-native-worklets": "0.7.1", + "react-native-worklets": "0.7.3", "react-native-safe-area-context": "5.6.2", - "react-native-screens": "4.18.0", + "react-native-screens": "4.23.0", "react-native-web": "0.21.2", "react-native-webview": "13.16.0", "react-native-zoom-toolkit": "5.0.1", - "react-window": "2.2.5", + "react-window": "2.2.6", "shallowequal": "1.1.0", "uint8array-extras": "1.5.0", "url-parse": "1.5.10", "use-debounce": "10.1.0", "util": "0.12.5", - "zustand": "5.0.10" + "zustand": "5.0.11" }, "devDependencies": { - "@babel/core": "7.28.5", - "@babel/node": "7.28.0", - "@babel/preset-env": "7.28.5", + "@babel/core": "7.29.0", + "@babel/node": "7.29.0", + "@babel/preset-env": "7.29.0", "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", "@electron/packager": "18.4.4", @@ -149,7 +149,7 @@ "@types/google-libphonenumber": "7.4.30", "@types/lodash": "4.17.23", "@types/lodash-es": "4.17.12", - "@types/react": "19.2.8", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/react-is": "19.2.0", "@types/react-list": "0.8.12", @@ -161,23 +161,22 @@ "babel-plugin-module-resolver": "5.0.2", "babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-native-web": "0.21.2", - "babel-preset-expo": "54.0.9", - "circular-dependency-plugin": "5.2.2", + "babel-preset-expo": "54.0.10", "cross-env": "7.0.3", - "css-loader": "7.1.2", - "electron": "39.2.7", - "eslint": "9.39.2", + "css-loader": "7.1.3", + "electron": "40.4.0", + "eslint": "10.0.0", "eslint-plugin-deprecation": "3.0.0", "eslint-plugin-promise": "7.2.1", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-hooks": "7.0.1", "fs-extra": "11.3.3", - "html-webpack-plugin": "5.6.5", + "html-webpack-plugin": "5.6.6", "json5": "2.2.3", "null-loader": "4.0.1", "patch-package": "8.0.1", - "prettier": "3.8.0", + "prettier": "3.8.1", "react-refresh": "0.18.0", "react-scan": "0.4.3", "react-test-renderer": "19.1.0", @@ -185,15 +184,15 @@ "style-loader": "4.0.0", "terser-webpack-plugin": "5.3.16", "typescript": "5.9.3", - "typescript-eslint": "8.53.0", - "webpack": "5.104.1", + "typescript-eslint": "8.55.0", + "webpack": "5.105.2", "webpack-cli": "6.0.1", "webpack-dev-server": "5.2.3", "webpack-merge": "6.0.1" }, "resolutions": { "**/purepack": "keybase/nullModule", - "**/@types/react": "19.2.8" + "**/@types/react": "19.2.14" }, "typecoverage": { "detail": true, diff --git a/shared/people/announcement.tsx b/shared/people/announcement.tsx index 7a6e55dd442c..d3807eff4157 100644 --- a/shared/people/announcement.tsx +++ b/shared/people/announcement.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import openURL from '@/util/open-url' import * as Kb from '@/common-adapters' import PeopleItem from './item' -import * as Settings from '@/constants/settings/util' -import {usePeopleState} from '@/constants/people' +import * as Settings from '@/constants/settings' +import {usePeopleState} from '@/stores/people' type OwnProps = { appLink?: T.RPCGen.AppLinkType diff --git a/shared/people/container.tsx b/shared/people/container.tsx index 6a5182b99177..fba20b5002cb 100644 --- a/shared/people/container.tsx +++ b/shared/people/container.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import People from '.' -import {useSignupState} from '@/constants/signup' -import {useProfileState} from '@/constants/profile' -import {usePeopleState, getPeopleDataWaitingKey} from '@/constants/people' -import {useCurrentUserState} from '@/constants/current-user' +import {useSignupState} from '@/stores/signup' +import {useProfileState} from '@/stores/profile' +import {usePeopleState, getPeopleDataWaitingKey} from '@/stores/people' +import {useCurrentUserState} from '@/stores/current-user' const waitToRefresh = 1000 * 60 * 5 diff --git a/shared/people/index.shared.tsx b/shared/people/index.shared.tsx index 66edb1f66e6d..e51585aa6339 100644 --- a/shared/people/index.shared.tsx +++ b/shared/people/index.shared.tsx @@ -7,8 +7,8 @@ import FollowNotification from './follow-notification' import FollowSuggestions from './follow-suggestions' import type {Props} from '.' import Todo from './todo' -import {useSignupState} from '@/constants/signup' -import {usePeopleState} from '@/constants/people' +import {useSignupState} from '@/stores/signup' +import {usePeopleState} from '@/stores/people' // import WotTask from './wot-task' const itemToComponent: (item: T.Immutable, props: Props) => React.ReactNode = ( diff --git a/shared/people/routes.tsx b/shared/people/routes.tsx index 1a73b36b39b9..f6c42a6f7051 100644 --- a/shared/people/routes.tsx +++ b/shared/people/routes.tsx @@ -3,7 +3,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import peopleTeamBuilder from '../team-building/page' import ProfileSearch from '../profile/search' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const HeaderAvatar = () => { const myUsername = useCurrentUserState(s => s.username) diff --git a/shared/people/todo.tsx b/shared/people/todo.tsx index 512f1545b35f..016bc55b3d41 100644 --- a/shared/people/todo.tsx +++ b/shared/people/todo.tsx @@ -1,18 +1,18 @@ import * as C from '@/constants' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import openURL from '@/util/open-url' import type * as T from '@/constants/types' import type {IconType} from '@/common-adapters/icon.constants-gen' import PeopleItem, {type TaskButton} from './item' import * as Kb from '@/common-adapters' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' -import {settingsAccountTab, settingsGitTab} from '@/constants/settings/util' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {usePeopleState, todoTypes} from '@/constants/people' -import {useCurrentUserState} from '@/constants/current-user' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' +import {settingsAccountTab, settingsGitTab} from '@/constants/settings' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {usePeopleState, todoTypes} from '@/stores/people' +import {useCurrentUserState} from '@/stores/current-user' type TodoOwnProps = { badged: boolean diff --git a/shared/pinentry/remote-container.desktop.tsx b/shared/pinentry/remote-container.desktop.tsx index 97f0967a1e16..316f528a9e14 100644 --- a/shared/pinentry/remote-container.desktop.tsx +++ b/shared/pinentry/remote-container.desktop.tsx @@ -3,7 +3,7 @@ import * as RemoteGen from '../actions/remote-gen' import * as R from '@/constants/remote' import Pinentry from './index.desktop' import type {DeserializeProps} from './remote-serializer.desktop' -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' const RemoteContainer = (d: DeserializeProps) => { const {darkMode, ...rest} = d diff --git a/shared/pinentry/remote-proxy.desktop.tsx b/shared/pinentry/remote-proxy.desktop.tsx index 587043c04c1d..ea897586694b 100644 --- a/shared/pinentry/remote-proxy.desktop.tsx +++ b/shared/pinentry/remote-proxy.desktop.tsx @@ -6,7 +6,7 @@ import useBrowserWindow from '../desktop/remote/use-browser-window.desktop' import useSerializeProps from '../desktop/remote/use-serialize-props.desktop' import {serialize, type ProxyProps} from './remote-serializer.desktop' import {useColorScheme} from 'react-native' -import {usePinentryState} from '@/constants/pinentry' +import {usePinentryState} from '@/stores/pinentry' const windowOpts = {height: 230, width: 440} diff --git a/shared/pinentry/remote-serializer.desktop.tsx b/shared/pinentry/remote-serializer.desktop.tsx index 96f859f93cce..ca291a3014af 100644 --- a/shared/pinentry/remote-serializer.desktop.tsx +++ b/shared/pinentry/remote-serializer.desktop.tsx @@ -1,5 +1,5 @@ import * as T from '@/constants/types' -import type * as Constants from '@/constants/pinentry' +import type * as Constants from '@/stores/pinentry' import {produce} from 'immer' export type ProxyProps = { diff --git a/shared/profile/add-to-team.tsx b/shared/profile/add-to-team.tsx index 0c0228be9967..a4581d07b18b 100644 --- a/shared/profile/add-to-team.tsx +++ b/shared/profile/add-to-team.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import {FloatingRolePicker, sendNotificationFooter} from '@/teams/role-picker' import * as Kb from '@/common-adapters' diff --git a/shared/profile/confirm-or-pending.tsx b/shared/profile/confirm-or-pending.tsx index e0a49954bb5a..f33f9325224e 100644 --- a/shared/profile/confirm-or-pending.tsx +++ b/shared/profile/confirm-or-pending.tsx @@ -1,4 +1,4 @@ -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {subtitle} from '@/util/platforms' diff --git a/shared/profile/edit-avatar/hooks.tsx b/shared/profile/edit-avatar/hooks.tsx index cc54782f2283..9e5778f69af5 100644 --- a/shared/profile/edit-avatar/hooks.tsx +++ b/shared/profile/edit-avatar/hooks.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' -import * as Teams from '@/constants/teams' +import {useProfileState} from '@/stores/profile' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {Props} from '.' diff --git a/shared/profile/edit-profile.tsx b/shared/profile/edit-profile.tsx index 61bb397bab2a..c153a4866cf4 100644 --- a/shared/profile/edit-profile.tsx +++ b/shared/profile/edit-profile.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' const Container = () => { const username = useCurrentUserState(s => s.username) diff --git a/shared/profile/generic/enter-username.tsx b/shared/profile/generic/enter-username.tsx index 41fee450433c..550411e67338 100644 --- a/shared/profile/generic/enter-username.tsx +++ b/shared/profile/generic/enter-username.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import openURL from '@/util/open-url' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/profile/generic/proofs-list.tsx b/shared/profile/generic/proofs-list.tsx index 20c8dae21a8a..ffbfa6fe9f16 100644 --- a/shared/profile/generic/proofs-list.tsx +++ b/shared/profile/generic/proofs-list.tsx @@ -5,8 +5,8 @@ import type * as T from '@/constants/types' import {SiteIcon} from './shared' import {makeInsertMatcher} from '@/util/string' import {useColorScheme} from 'react-native' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' const Container = () => { const _proofSuggestions = useTrackerState(s => s.proofSuggestions) diff --git a/shared/profile/generic/result.tsx b/shared/profile/generic/result.tsx index db0e0b49a419..765700b8b568 100644 --- a/shared/profile/generic/result.tsx +++ b/shared/profile/generic/result.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import {SiteIcon} from './shared' diff --git a/shared/profile/pgp/finished/index.desktop.tsx b/shared/profile/pgp/finished/index.desktop.tsx index 89e7f6c192cb..22e9341df3e8 100644 --- a/shared/profile/pgp/finished/index.desktop.tsx +++ b/shared/profile/pgp/finished/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import Modal from '@/profile/modal' diff --git a/shared/profile/pgp/generate/index.desktop.tsx b/shared/profile/pgp/generate/index.desktop.tsx index ed1be8203b1a..b144be1e0b6b 100644 --- a/shared/profile/pgp/generate/index.desktop.tsx +++ b/shared/profile/pgp/generate/index.desktop.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import Modal from '@/profile/modal' export default function Generate() { diff --git a/shared/profile/pgp/info/index.desktop.tsx b/shared/profile/pgp/info/index.desktop.tsx index af9859070160..8da249f27ec5 100644 --- a/shared/profile/pgp/info/index.desktop.tsx +++ b/shared/profile/pgp/info/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import Modal from '@/profile/modal' diff --git a/shared/profile/post-proof.tsx b/shared/profile/post-proof.tsx index ddcc5b7e3b1c..74f1cc9fd63c 100644 --- a/shared/profile/post-proof.tsx +++ b/shared/profile/post-proof.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import {subtitle} from '@/util/platforms' import openUrl from '@/util/open-url' import Modal from './modal' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const Container = () => { const platform = useProfileState(s => s.platform) @@ -49,7 +49,7 @@ const Container = () => { break } const platformUserName = username - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const clearModals = C.useRouterState(s => s.dispatch.clearModals) const onCancel = () => { clearModals() diff --git a/shared/profile/prove-enter-username.tsx b/shared/profile/prove-enter-username.tsx index f64207564d86..5bd8cc0d1398 100644 --- a/shared/profile/prove-enter-username.tsx +++ b/shared/profile/prove-enter-username.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import Modal from './modal' diff --git a/shared/profile/prove-website-choice.tsx b/shared/profile/prove-website-choice.tsx index 7eabba0143a9..ff6e664c9f14 100644 --- a/shared/profile/prove-website-choice.tsx +++ b/shared/profile/prove-website-choice.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import Modal from './modal' diff --git a/shared/profile/revoke.tsx b/shared/profile/revoke.tsx index 5fa5097c28f8..aa34864f41ae 100644 --- a/shared/profile/revoke.tsx +++ b/shared/profile/revoke.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import capitalize from 'lodash/capitalize' import {subtitle as platformSubtitle} from '@/util/platforms' diff --git a/shared/profile/showcase-team-offer.tsx b/shared/profile/showcase-team-offer.tsx index 0041a0725477..12c43db82598 100644 --- a/shared/profile/showcase-team-offer.tsx +++ b/shared/profile/showcase-team-offer.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import {useTeamsSubscribe} from '@/teams/subscriber' -import {useTrackerState} from '@/constants/tracker2' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useCurrentUserState} from '@/stores/current-user' const Container = () => { const waiting = C.useWaitingState(s => s.counts) diff --git a/shared/profile/user/actions/index.tsx b/shared/profile/user/actions/index.tsx index 5695e6e9051c..2dcf058334ef 100644 --- a/shared/profile/user/actions/index.tsx +++ b/shared/profile/user/actions/index.tsx @@ -4,11 +4,11 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import FollowButton from './follow-button' import ChatButton from '@/chat/chat-button' -import {useBotsState} from '@/constants/bots' -import {useTrackerState} from '@/constants/tracker2' -import * as FS from '@/constants/fs' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useBotsState} from '@/stores/bots' +import {useTrackerState} from '@/stores/tracker2' +import * as FS from '@/stores/fs' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {username: string} @@ -28,7 +28,7 @@ const Container = (ownProps: OwnProps) => { const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) const _onAddToTeam = (username: string) => navigateAppend({props: {username}, selected: 'profileAddToTeam'}) const _onBrowsePublicFolder = (username: string) => - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/public/${username}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/public/${username}`)) const _onEditProfile = () => navigateAppend('profileEdit') const changeFollow = useTrackerState(s => s.dispatch.changeFollow) @@ -39,7 +39,7 @@ const Container = (ownProps: OwnProps) => { const _onManageBlocking = (username: string) => navigateAppend({props: {username}, selected: 'chatBlockingModal'}) const _onOpenPrivateFolder = (myUsername: string, theirUsername: string) => - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/private/${theirUsername},${myUsername}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/private/${theirUsername},${myUsername}`)) const showUser = useTrackerState(s => s.dispatch.showUser) const _onReload = (username: string) => { showUser(username, false) diff --git a/shared/profile/user/friend.tsx b/shared/profile/user/friend.tsx index ef0815a24d86..462965aaa940 100644 --- a/shared/profile/user/friend.tsx +++ b/shared/profile/user/friend.tsx @@ -1,6 +1,6 @@ -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' type OwnProps = { username: string diff --git a/shared/profile/user/hooks.tsx b/shared/profile/user/hooks.tsx index 0a2a49b3017e..b3848ac98bbd 100644 --- a/shared/profile/user/hooks.tsx +++ b/shared/profile/user/hooks.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import type * as T from '@/constants/types' import {type BackgroundColorType} from '.' import {useColorScheme} from 'react-native' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' const headerBackgroundColorType = ( state: T.Tracker.DetailsState, diff --git a/shared/profile/user/teams/index.tsx b/shared/profile/user/teams/index.tsx index 7cbc8c427ee2..f78cfca43e24 100644 --- a/shared/profile/user/teams/index.tsx +++ b/shared/profile/user/teams/index.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' import OpenMeta from './openmeta' import {default as TeamInfo, type Props as TIProps} from './teaminfo' -import {useTrackerState} from '@/constants/tracker2' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {username: string} diff --git a/shared/provision/code-page/container.tsx b/shared/provision/code-page/container.tsx index bc731a76d534..a33c2113e0da 100644 --- a/shared/provision/code-page/container.tsx +++ b/shared/provision/code-page/container.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as React from 'react' import * as Kb from '@/common-adapters' import QRImage from './qr-image' import QRScan from './qr-scan' import Troubleshooting from '../troubleshooting' import type * as T from '@/constants/types' -import {useCurrentUserState} from '@/constants/current-user' -import {type Device, useProvisionState} from '@/constants/provision' +import {useCurrentUserState} from '@/stores/current-user' +import {type Device, useProvisionState} from '@/stores/provision' const CodePageContainer = () => { const {deviceID, storeDeviceName} = useCurrentUserState( diff --git a/shared/provision/code-page/qr-scan/hooks.tsx b/shared/provision/code-page/qr-scan/hooks.tsx index bf1687854d0b..c6515c36c5d4 100644 --- a/shared/provision/code-page/qr-scan/hooks.tsx +++ b/shared/provision/code-page/qr-scan/hooks.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const useQR = () => { const submitTextCode = useProvisionState(s => s.dispatch.dynamic.submitTextCode) diff --git a/shared/provision/code-page/qr-scan/not-authorized.tsx b/shared/provision/code-page/qr-scan/not-authorized.tsx index ee5774db4a22..cdd21978b3b4 100644 --- a/shared/provision/code-page/qr-scan/not-authorized.tsx +++ b/shared/provision/code-page/qr-scan/not-authorized.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const QRScanNotAuthorized = () => { - const onOpenSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenSettings = useConfigState(s => s.dispatch.defer.openAppSettings) return ( diff --git a/shared/provision/error.tsx b/shared/provision/error.tsx index 93125fdfb30b..a5cafc24ae99 100644 --- a/shared/provision/error.tsx +++ b/shared/provision/error.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as Kb from '@/common-adapters' import type * as React from 'react' import LoginContainer from '../login/forms/container' import openURL from '@/util/open-url' import * as T from '@/constants/types' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const Wrapper = (p: {onBack: () => void; children: React.ReactNode}) => ( diff --git a/shared/provision/forgot-username.tsx b/shared/provision/forgot-username.tsx index 9b01068677b6..ad746c8e7579 100644 --- a/shared/provision/forgot-username.tsx +++ b/shared/provision/forgot-username.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen, errorBanner} from '../signup/common' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useProvisionState} from '@/constants/provision' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useProvisionState} from '@/stores/provision' const ForgotUsername = () => { const defaultCountry = useSettingsPhoneState(s => s.defaultCountry) diff --git a/shared/provision/paper-key.tsx b/shared/provision/paper-key.tsx index 8fb1972d6f6a..0ae84be9dc0d 100644 --- a/shared/provision/paper-key.tsx +++ b/shared/provision/paper-key.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import {SignupScreen, errorBanner} from '../signup/common' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const Container = () => { const error = useProvisionState(s => s.error) diff --git a/shared/provision/password.tsx b/shared/provision/password.tsx index 3d1f2adddff5..3bef094b69fc 100644 --- a/shared/provision/password.tsx +++ b/shared/provision/password.tsx @@ -3,8 +3,8 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import UserCard from '../login/user-card' import {SignupScreen, errorBanner} from '../signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useProvisionState} from '@/constants/provision' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useProvisionState} from '@/stores/provision' const Password = () => { const error = useProvisionState(s => s.error) diff --git a/shared/provision/select-other-device-connected.tsx b/shared/provision/select-other-device-connected.tsx index 6f1baa6a45cc..69499e1c7547 100644 --- a/shared/provision/select-other-device-connected.tsx +++ b/shared/provision/select-other-device-connected.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import {useSafeSubmit} from '@/util/safe-submit' import SelectOtherDevice from './select-other-device' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const SelectOtherDeviceContainer = () => { const devices = useProvisionState(s => s.devices) diff --git a/shared/provision/select-other-device.tsx b/shared/provision/select-other-device.tsx index b39e8c25308e..3ac49ebaba69 100644 --- a/shared/provision/select-other-device.tsx +++ b/shared/provision/select-other-device.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import DeviceIcon from '../devices/device-icon' import {SignupScreen} from '../signup/common' -import {type Device} from '@/constants/provision' +import {type Device} from '@/stores/provision' type Props = { passwordRecovery?: boolean diff --git a/shared/provision/set-public-name.tsx b/shared/provision/set-public-name.tsx index d25bc7c81f22..3b5d7ba52058 100644 --- a/shared/provision/set-public-name.tsx +++ b/shared/provision/set-public-name.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import {useSafeSubmit} from '@/util/safe-submit' import * as Kb from '@/common-adapters' import * as React from 'react' import debounce from 'lodash/debounce' import {SignupScreen, errorBanner} from '../signup/common' -import * as Provision from '@/constants/provision' +import * as Provision from '@/stores/provision' const SetPublicName = () => { const devices = Provision.useProvisionState(s => s.devices) diff --git a/shared/provision/troubleshooting.tsx b/shared/provision/troubleshooting.tsx index 0f00a0cc0e8b..e7aa4f2318c7 100644 --- a/shared/provision/troubleshooting.tsx +++ b/shared/provision/troubleshooting.tsx @@ -1,9 +1,9 @@ import * as React from 'react' import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' type Props = { mode: 'QR' | 'text' onCancel: () => void diff --git a/shared/provision/username-or-email.tsx b/shared/provision/username-or-email.tsx index 0ead30e44502..fd06d797850d 100644 --- a/shared/provision/username-or-email.tsx +++ b/shared/provision/username-or-email.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' -import {useSignupState} from '@/constants/signup' +import * as AutoReset from '@/stores/autoreset' +import {useSignupState} from '@/stores/signup' import {useSafeSubmit} from '@/util/safe-submit' import * as T from '@/constants/types' import * as React from 'react' @@ -8,9 +8,9 @@ import type {RPCError} from '@/util/errors' import * as Kb from '@/common-adapters' import UserCard from '@/login/user-card' import {SignupScreen, errorBanner} from '@/signup/common' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' -type OwnProps = {fromReset?: boolean} +type OwnProps = {fromReset?: boolean; username?: string} const decodeInlineError = (inlineRPCError: RPCError | undefined) => { let inlineError = '' @@ -59,7 +59,12 @@ const UsernameOrEmailContainer = (op: OwnProps) => { }, [_setUsername, waiting] ) - const [username, setUsername] = React.useState(_username) + const [username, setUsername] = React.useState(op.username ?? _username) + React.useEffect(() => { + if (op.username && op.username !== _username) { + _setUsername?.(op.username) + } + }, [op.username, _username, _setUsername]) const onSubmit = React.useCallback(() => { _onSubmit(username) }, [_onSubmit, username]) diff --git a/shared/router-v2/account-switcher/index.tsx b/shared/router-v2/account-switcher/index.tsx index 7ed7d926f309..f4a67159ff4d 100644 --- a/shared/router-v2/account-switcher/index.tsx +++ b/shared/router-v2/account-switcher/index.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' import './account-switcher.css' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' -import {settingsLogOutTab} from '@/constants/settings/util' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' -import {useProvisionState} from '@/constants/provision' +import {settingsLogOutTab} from '@/constants/settings' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' +import {useProvisionState} from '@/stores/provision' const prepareAccountRows = ( accountRows: ReadonlyArray, diff --git a/shared/router-v2/common.native.tsx b/shared/router-v2/common.native.tsx index af2f2056c052..8037a3caaedc 100644 --- a/shared/router-v2/common.native.tsx +++ b/shared/router-v2/common.native.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import {TabActions, type NavigationContainerRef} from '@react-navigation/core' import type {HeaderOptions} from '@react-navigation/elements' import {HeaderLeftArrowCanGoBack} from '@/common-adapters/header-hoc' -import type {NavState} from '@/constants/router2' +import type {NavState} from '@/stores/router2' export const headerDefaultStyle = { get backgroundColor() { diff --git a/shared/router-v2/header/index.desktop.tsx b/shared/router-v2/header/index.desktop.tsx index 2a46eb9be1c3..892c4fbde2a4 100644 --- a/shared/router-v2/header/index.desktop.tsx +++ b/shared/router-v2/header/index.desktop.tsx @@ -6,7 +6,7 @@ import {IconWithPopupDesktop as WhatsNewIconWithPopup} from '@/whats-new/icon' import * as ReactIs from 'react-is' import KB2 from '@/util/electron.desktop' import shallowEqual from 'shallowequal' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const {closeWindow, minimizeWindow, toggleMaximizeWindow} = KB2.functions diff --git a/shared/router-v2/header/syncing-folders.tsx b/shared/router-v2/header/syncing-folders.tsx index 578aa416f544..722a91e2bb98 100644 --- a/shared/router-v2/header/syncing-folders.tsx +++ b/shared/router-v2/header/syncing-folders.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as Constants from '@/constants/fs' +import * as Constants from '@/stores/fs' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import PieSlice from '@/fs/common/pie-slice' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { negative?: boolean diff --git a/shared/router-v2/hooks.native.tsx b/shared/router-v2/hooks.native.tsx index e957781818b8..0c6ec0747e58 100644 --- a/shared/router-v2/hooks.native.tsx +++ b/shared/router-v2/hooks.native.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as Tabs from '@/constants/tabs' import * as React from 'react' -import {useDeepLinksState} from '@/constants/deeplinks' +import {handleAppLink} from '@/constants/deeplinks' import {Linking} from 'react-native' import {useColorScheme} from 'react-native' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' type InitialStateState = 'init' | 'loading' | 'loaded' @@ -112,7 +112,7 @@ export const useInitialState = (loggedInLoaded: boolean) => { } if (url && isValidLink(url)) { - setTimeout(() => url && useDeepLinksState.getState().dispatch.handleAppLink(url), 1) + setTimeout(() => url && handleAppLink(url), 1) } else if (startupFollowUser && !startupConversation) { url = `keybase://profile/show/${startupFollowUser}` diff --git a/shared/router-v2/left-tab-navigator.desktop.tsx b/shared/router-v2/left-tab-navigator.desktop.tsx index bac3cb3c04a5..a30eceb6a845 100644 --- a/shared/router-v2/left-tab-navigator.desktop.tsx +++ b/shared/router-v2/left-tab-navigator.desktop.tsx @@ -4,7 +4,8 @@ import TabBar from './tab-bar.desktop' import {useNavigationBuilder, TabRouter, createNavigatorFactory} from '@react-navigation/core' import type {TypedNavigator, NavigatorTypeBagBase, StaticConfig} from '@react-navigation/native' import type * as Tabs from '@/constants/tabs' -import * as Router2 from '@/constants/router2' +import {useRouterState} from '@/stores/router2' +import {getModalStack} from '@/constants/router2' type BackBehavior = Parameters[0]['backBehavior'] type Props = Parameters[1] & {backBehavior: BackBehavior} @@ -50,7 +51,7 @@ const LeftTabNavigator = React.memo(function LeftTabNavigator({ setRendered(next) }, [selectedRoute, rendered]) - const hasModals = Router2.useRouterState(() => Router2.getModalStack().length > 0) + const hasModals = useRouterState(() => getModalStack().length > 0) return ( diff --git a/shared/router-v2/router.desktop.tsx b/shared/router-v2/router.desktop.tsx index 3173b32abf55..51816647e878 100644 --- a/shared/router-v2/router.desktop.tsx +++ b/shared/router-v2/router.desktop.tsx @@ -1,6 +1,6 @@ import * as Common from './common.desktop' import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import * as React from 'react' import * as Shared from './router.shared' @@ -15,8 +15,8 @@ import {createNativeStackNavigator} from '@react-navigation/native-stack' import {modalRoutes, routes, loggedOutRoutes, tabRoots} from './routes' import {registerDebugClear} from '@/util/debug' import type {RootParamList} from '@/router-v2/route-params' -import {useCurrentUserState} from '@/constants/current-user' -import {useDaemonState} from '@/constants/daemon' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' import type {NativeStackNavigationOptions} from '@react-navigation/native-stack' import './router.css' diff --git a/shared/router-v2/router.native.tsx b/shared/router-v2/router.native.tsx index f807285c2920..53099d8043d7 100644 --- a/shared/router-v2/router.native.tsx +++ b/shared/router-v2/router.native.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Constants from '@/constants/router2' -import {useConfigState} from '@/constants/config' -import {useDarkModeState} from '@/constants/darkmode' +import * as Constants from '@/stores/router2' +import {useConfigState} from '@/stores/config' +import {useDarkModeState} from '@/stores/darkmode' import * as Kb from '@/common-adapters' import * as React from 'react' import * as Shared from './router.shared' @@ -20,7 +20,7 @@ import * as Hooks from './hooks.native' import * as TabBar from './tab-bar.native' import type {RootParamList} from '@/router-v2/route-params' import {useColorScheme} from 'react-native' -import {useDaemonState} from '@/constants/daemon' +import {useDaemonState} from '@/stores/daemon' if (module.hot) { module.hot.accept('', () => {}) @@ -68,7 +68,7 @@ const tabStacks = tabs.map(tab => ( name={tab} listeners={{ tabLongPress: () => { - C.useRouterState.getState().dispatch.dynamic.tabLongPress?.(tab) + C.useRouterState.getState().dispatch.defer.tabLongPress?.(tab) }, }} component={TabStack} diff --git a/shared/router-v2/router.shared.tsx b/shared/router-v2/router.shared.tsx index 7a88668f6157..e0bb7847af68 100644 --- a/shared/router-v2/router.shared.tsx +++ b/shared/router-v2/router.shared.tsx @@ -4,8 +4,8 @@ import * as React from 'react' import {Splash} from '../login/loading' import type {Theme} from '@react-navigation/native' import {colors, darkColors, themed} from '@/styles/colors' -import {useFSState} from '@/constants/fs' -import {useDarkModeState} from '@/constants/darkmode' +import {useFSState} from '@/stores/fs' +import {useDarkModeState} from '@/stores/darkmode' export const SimpleLoading = React.memo(function SimpleLoading() { return ( diff --git a/shared/router-v2/tab-bar.desktop.tsx b/shared/router-v2/tab-bar.desktop.tsx index 843f25c36ead..67baf715faf2 100644 --- a/shared/router-v2/tab-bar.desktop.tsx +++ b/shared/router-v2/tab-bar.desktop.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kbfs from '@/fs/common' import * as Platforms from '@/constants/platform' import * as T from '@/constants/types' @@ -13,13 +13,13 @@ import openURL from '@/util/open-url' import {isLinux} from '@/constants/platform' import KB2 from '@/util/electron.desktop' import './tab-bar.css' -import {settingsLogOutTab} from '@/constants/settings/util' -import {useTrackerState} from '@/constants/tracker2' -import {useFSState} from '@/constants/fs' -import {useProfileState} from '@/constants/profile' -import {useNotifState} from '@/constants/notifications' -import {useCurrentUserState} from '@/constants/current-user' -import {useProvisionState} from '@/constants/provision' +import {settingsLogOutTab} from '@/constants/settings' +import {useTrackerState} from '@/stores/tracker2' +import {useFSState} from '@/stores/fs' +import {useProfileState} from '@/stores/profile' +import {useNotifState} from '@/stores/notifications' +import {useCurrentUserState} from '@/stores/current-user' +import {useProvisionState} from '@/stores/provision' const {hideWindow, ctlQuit} = KB2.functions diff --git a/shared/router-v2/tab-bar.native.tsx b/shared/router-v2/tab-bar.native.tsx index 71bb34a5ff8f..30671cb861f4 100644 --- a/shared/router-v2/tab-bar.native.tsx +++ b/shared/router-v2/tab-bar.native.tsx @@ -6,8 +6,8 @@ import * as Shared from './router.shared' import {View} from 'react-native' import {useSafeAreaFrame} from 'react-native-safe-area-context' import {useColorScheme} from 'react-native' -import {usePushState} from '@/constants/push' -import {useNotifState} from '@/constants/notifications' +import {usePushState} from '@/stores/push' +import {useNotifState} from '@/stores/notifications' const settingsTabChildren = [Tabs.gitTab, Tabs.devicesTab, Tabs.settingsTab] as const const tabs = C.isTablet ? Tabs.tabletTabs : Tabs.phoneTabs diff --git a/shared/settings/account/add-modals.tsx b/shared/settings/account/add-modals.tsx index f5736b62172a..5d203251e85d 100644 --- a/shared/settings/account/add-modals.tsx +++ b/shared/settings/account/add-modals.tsx @@ -6,8 +6,8 @@ import {EnterEmailBody} from '@/signup/email' import {EnterPhoneNumberBody} from '@/signup/phone-number' import VerifyBody from '@/signup/phone-number/verify-body' import {e164ToDisplay} from '@/util/phone-numbers' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' export const Email = () => { const nav = useSafeNavigation() diff --git a/shared/settings/account/confirm-delete.tsx b/shared/settings/account/confirm-delete.tsx index cec9eaa58255..1b4fa2d6f9aa 100644 --- a/shared/settings/account/confirm-delete.tsx +++ b/shared/settings/account/confirm-delete.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as PhoneUtil from '@/util/phone-numbers' import {useSafeNavigation} from '@/util/safe-navigation' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' type OwnProps = { address: string diff --git a/shared/settings/account/email-phone-row.tsx b/shared/settings/account/email-phone-row.tsx index b3b7f6e32a3d..1eec1a6336e7 100644 --- a/shared/settings/account/email-phone-row.tsx +++ b/shared/settings/account/email-phone-row.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' const addSpacer = (into: string, add: string) => { return into + (into.length ? ' • ' : '') + add diff --git a/shared/settings/account/index.tsx b/shared/settings/account/index.tsx index 1cc8a39c9352..c45c1d9725b0 100644 --- a/shared/settings/account/index.tsx +++ b/shared/settings/account/index.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import type * as React from 'react' import EmailPhoneRow from './email-phone-row' -import {usePWState} from '@/constants/settings-password' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' -import {useSettingsState, settingsPasswordTab} from '@/constants/settings' +import {usePWState} from '@/stores/settings-password' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSettingsState, settingsPasswordTab} from '@/stores/settings' export const SettingsSection = ({children}: {children: React.ReactNode}) => ( diff --git a/shared/settings/advanced.tsx b/shared/settings/advanced.tsx index cb9414ddbd4d..7f926fdc196a 100644 --- a/shared/settings/advanced.tsx +++ b/shared/settings/advanced.tsx @@ -3,10 +3,10 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' import {ProxySettings} from './proxy' -import {useSettingsState, traceInProgressKey, processorProfileInProgressKey} from '@/constants/settings' -import {usePWState} from '@/constants/settings-password' -import {useFSState} from '@/constants/fs' -import {useConfigState} from '@/constants/config' +import {useSettingsState, traceInProgressKey, processorProfileInProgressKey} from '@/stores/settings' +import {usePWState} from '@/stores/settings-password' +import {useFSState} from '@/stores/fs' +import {useConfigState} from '@/stores/config' let initialUseNativeFrame: boolean | undefined diff --git a/shared/settings/archive/index.tsx b/shared/settings/archive/index.tsx index 3ec5cf413eee..ab07df9512c9 100644 --- a/shared/settings/archive/index.tsx +++ b/shared/settings/archive/index.tsx @@ -3,9 +3,10 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {formatTimeForConversationList, formatTimeForChat} from '@/util/timestamp' -import {useArchiveState} from '@/constants/archive' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import {useArchiveState} from '@/stores/archive' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' +import {showShareActionSheet} from '@/util/platform-specific' const ChatJob = React.memo(function ChatJob(p: {index: number; id: string}) { const {id, index} = p @@ -29,7 +30,7 @@ const ChatJob = React.memo(function ChatJob(p: {index: number; id: string}) { resume(id) }, [resume, id]) - const openFinder = useFSState(s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop) + const openFinder = useFSState(s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop) const onShowFinder = React.useCallback(() => { if (!job) return openFinder?.(job.outPath) @@ -37,7 +38,7 @@ const ChatJob = React.memo(function ChatJob(p: {index: number; id: string}) { const onShare = React.useCallback(() => { if (!job?.outPath) return - C.PlatformSpecific.showShareActionSheet({ + showShareActionSheet({ filePath: job.outPath, mimeType: 'application/zip', }) @@ -152,7 +153,7 @@ const KBFSJob = React.memo(function KBFSJob(p: {index: number; id: string}) { loadKBFSJobFreshness(id) }) - const openFinder = useFSState(s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop) + const openFinder = useFSState(s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop) const onShowFinder = React.useCallback(() => { if (Kb.Styles.isMobile || !job) { return @@ -164,10 +165,7 @@ const KBFSJob = React.memo(function KBFSJob(p: {index: number; id: string}) { if (!Kb.Styles.isMobile || !job) { return } - C.PlatformSpecific.showShareActionSheet({ - filePath: job.zipFilePath, - mimeType: 'application/zip', - }) + showShareActionSheet({filePath: job.zipFilePath, mimeType: 'application/zip'}) .then(() => {}) .catch(() => {}) }, [job]) diff --git a/shared/settings/archive/modal.tsx b/shared/settings/archive/modal.tsx index ff13598e2693..1e77cb8b0a8c 100644 --- a/shared/settings/archive/modal.tsx +++ b/shared/settings/archive/modal.tsx @@ -4,8 +4,10 @@ import * as C from '@/constants' import type * as T from '@/constants/types' import {pickSave} from '@/util/pick-files' import * as FsCommon from '@/fs/common' -import {useArchiveState} from '@/constants/archive' -import {settingsArchiveTab} from '@/constants/settings' +import {useArchiveState} from '@/stores/archive' +import {settingsArchiveTab} from '@/stores/settings' +import {useCurrentUserState} from '@/stores/current-user' +import {getConvoState} from '@/stores/convostate' type Props = | {type: 'chatID'; conversationIDKey: T.Chat.ConversationIDKey} @@ -16,12 +18,29 @@ type Props = | {type: 'fsPath'; path: string} | {type: 'git'; gitURL: string} +const chatIDToDisplayname = (conversationIDKey: string) => { + const you = useCurrentUserState.getState().username + const cs = getConvoState(conversationIDKey) + const m = cs.meta + if (m.teamname) { + if (m.channelname) { + return `${m.teamname}#${m.channelname}` + } + return m.teamname + } + + const participants = cs.participants.name + if (participants.length === 1) { + return participants[0] ?? '' + } + return participants.filter(username => username !== you).join(',') +} + const ArchiveModal = (p: Props) => { const {type} = p - const chatIDToDisplayname = useArchiveState(s => s.chatIDToDisplayname) const displayname = React.useMemo(() => { return p.type === 'chatID' ? chatIDToDisplayname(p.conversationIDKey) : '' - }, [p, chatIDToDisplayname]) + }, [p]) let defaultPath = '' if (C.isElectron) { diff --git a/shared/settings/chat.tsx b/shared/settings/chat.tsx index 5a0fc327fbe0..1759ee2dbaa1 100644 --- a/shared/settings/chat.tsx +++ b/shared/settings/chat.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import * as React from 'react' import Group from './group' -import {useSettingsChatState as useSettingsChatState} from '@/constants/settings-chat' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState} from '@/constants/settings' -import {useConfigState} from '@/constants/config' +import {useSettingsChatState as useSettingsChatState} from '@/stores/settings-chat' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState} from '@/stores/settings' +import {useConfigState} from '@/stores/config' const emptyList = new Array() diff --git a/shared/settings/contacts-joined.tsx b/shared/settings/contacts-joined.tsx index 0650fa5d2e47..994dcd5a5a85 100644 --- a/shared/settings/contacts-joined.tsx +++ b/shared/settings/contacts-joined.tsx @@ -4,9 +4,9 @@ import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' import * as React from 'react' import UnconnectedFollowButton from '@/profile/user/actions/follow-button' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {useTrackerState} from '@/constants/tracker2' -import {useFollowerState} from '@/constants/followers' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useTrackerState} from '@/stores/tracker2' +import {useFollowerState} from '@/stores/followers' const renderItem = (_: number, item: T.RPCGen.ProcessedContact) => diff --git a/shared/settings/db-nuke.confirm.tsx b/shared/settings/db-nuke.confirm.tsx index 5c4b71083a5d..d286b5367ddd 100644 --- a/shared/settings/db-nuke.confirm.tsx +++ b/shared/settings/db-nuke.confirm.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const DbNukeConfirm = () => { const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) diff --git a/shared/settings/delete-confirm/check-passphrase.native.tsx b/shared/settings/delete-confirm/check-passphrase.native.tsx index a861c09207b8..00010f8bf4db 100644 --- a/shared/settings/delete-confirm/check-passphrase.native.tsx +++ b/shared/settings/delete-confirm/check-passphrase.native.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const CheckPassphraseMobile = () => { const [password, setPassword] = React.useState('') diff --git a/shared/settings/delete-confirm/index.tsx b/shared/settings/delete-confirm/index.tsx index 1a4a243a2ca5..e946c68aa858 100644 --- a/shared/settings/delete-confirm/index.tsx +++ b/shared/settings/delete-confirm/index.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {usePWState} from '@/constants/settings-password' -import {useSettingsState} from '@/constants/settings' -import {useCurrentUserState} from '@/constants/current-user' +import {usePWState} from '@/stores/settings-password' +import {useSettingsState} from '@/stores/settings' +import {useCurrentUserState} from '@/stores/current-user' type CheckboxesProps = { checkData: boolean diff --git a/shared/settings/disable-cert-pinning-modal.tsx b/shared/settings/disable-cert-pinning-modal.tsx index b84956f0114d..8fe63caf18c2 100644 --- a/shared/settings/disable-cert-pinning-modal.tsx +++ b/shared/settings/disable-cert-pinning-modal.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const DisableCertPinningModal = () => { const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) diff --git a/shared/settings/display.tsx b/shared/settings/display.tsx index b95ba9a6e786..f7ab52aafed4 100644 --- a/shared/settings/display.tsx +++ b/shared/settings/display.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import logger from '@/logger' -import {useConfigState} from '@/constants/config' -import * as DarkMode from '@/constants/darkmode' +import {useConfigState} from '@/stores/config' +import * as DarkMode from '@/stores/darkmode' const Display = () => { const allowAnimatedEmojis = useConfigState(s => s.allowAnimatedEmojis) diff --git a/shared/settings/feedback/container.desktop.tsx b/shared/settings/feedback/container.desktop.tsx index 83933ea0ad0d..5ebef2dcdd73 100644 --- a/shared/settings/feedback/container.desktop.tsx +++ b/shared/settings/feedback/container.desktop.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import Feedback from '.' import type {Props} from './container' import {useSendFeedback} from './shared' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const Container = (ownProps: Props) => { const {sendFeedback, error} = useSendFeedback() diff --git a/shared/settings/feedback/container.native.tsx b/shared/settings/feedback/container.native.tsx index b88db320114e..a504f4ab065a 100644 --- a/shared/settings/feedback/container.native.tsx +++ b/shared/settings/feedback/container.native.tsx @@ -1,5 +1,5 @@ -import {useConfigState} from '@/constants/config' -import {useCurrentUserState} from '@/constants/current-user' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' import * as Kb from '@/common-adapters' import * as React from 'react' import Feedback from '.' @@ -9,7 +9,7 @@ import {getExtraChatLogsForLogSend} from './shared' import {isAndroid, version, pprofDir} from '@/constants/platform' import {logSend, appVersionName, appVersionCode} from 'react-native-kb' import type {Props as OwnProps} from './container' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' export type Props = { chat: object diff --git a/shared/settings/files/hooks.tsx b/shared/settings/files/hooks.tsx index 7cd0b2d730c2..e8e29a846900 100644 --- a/shared/settings/files/hooks.tsx +++ b/shared/settings/files/hooks.tsx @@ -1,6 +1,6 @@ import {defaultNotificationThreshold} from '.' import * as C from '@/constants' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const useFiles = () => { const {areSettingsLoading, setSpaceAvailableNotificationThreshold, spaceAvailableNotificationThreshold} = diff --git a/shared/settings/files/index.desktop.tsx b/shared/settings/files/index.desktop.tsx index c8abd4d17097..f62f7811fba2 100644 --- a/shared/settings/files/index.desktop.tsx +++ b/shared/settings/files/index.desktop.tsx @@ -6,8 +6,8 @@ import * as Kbfs from '@/fs/common' import RefreshDriverStatusOnMount from '@/fs/common/refresh-driver-status-on-mount' import RefreshSettings from './refresh-settings' import useFiles from './hooks' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = ReturnType export const allowedNotificationThresholds = [100 * 1024 ** 2, 1024 ** 3, 3 * 1024 ** 3, 10 * 1024 ** 3] @@ -58,7 +58,7 @@ const FinderIntegration = () => { C.useShallow(s => ({ driverDisable: s.dispatch.driverDisable, driverStatus: s.sfmi.driverStatus, - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, preferredMountDirs: s.sfmi.preferredMountDirs, })) ) diff --git a/shared/settings/files/index.native.tsx b/shared/settings/files/index.native.tsx index 66ecd2edf2f0..bbec8c0123d0 100644 --- a/shared/settings/files/index.native.tsx +++ b/shared/settings/files/index.native.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import useFiles from './hooks' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = ReturnType export const allowedNotificationThresholds = [100 * 1024 ** 2, 1024 ** 3, 3 * 1024 ** 3, 10 * 1024 ** 3] diff --git a/shared/settings/files/refresh-settings.tsx b/shared/settings/files/refresh-settings.tsx index aa4da1e72b56..383484e7b579 100644 --- a/shared/settings/files/refresh-settings.tsx +++ b/shared/settings/files/refresh-settings.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const RefreshSettings = () => { const refresh = useFSState(s => s.dispatch.loadSettings) diff --git a/shared/settings/group.tsx b/shared/settings/group.tsx index 839ad9fa16b1..fefd140db264 100644 --- a/shared/settings/group.tsx +++ b/shared/settings/group.tsx @@ -1,5 +1,5 @@ import * as Kb from '@/common-adapters' -import type {NotificationsSettingsState} from '@/constants/settings-notifications' +import type {NotificationsSettingsState} from '@/stores/settings-notifications' type GroupProps = { allowEdit: boolean diff --git a/shared/settings/invites/index.desktop.tsx b/shared/settings/invites/index.desktop.tsx index 0636b633ea3b..c9dd76394d51 100644 --- a/shared/settings/invites/index.desktop.tsx +++ b/shared/settings/invites/index.desktop.tsx @@ -1,11 +1,11 @@ import * as Kb from '@/common-adapters' -import type {AcceptedInvite, PendingInvite} from '@/constants/settings-invites' +import type {AcceptedInvite, PendingInvite} from '@/stores/settings-invites' import * as React from 'react' import SubHeading from '../subheading' import * as dateFns from 'date-fns' import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' -import {useState as useSettingsInvitesState} from '@/constants/settings-invites' +import {useProfileState} from '@/stores/profile' +import {useState as useSettingsInvitesState} from '@/stores/settings-invites' // Like intersperse but takes a function to define the separator function intersperseFn( diff --git a/shared/settings/logout.tsx b/shared/settings/logout.tsx index b5d765d9287c..c61c62e3656e 100644 --- a/shared/settings/logout.tsx +++ b/shared/settings/logout.tsx @@ -3,9 +3,9 @@ import {useSafeSubmit} from '@/util/safe-submit' import * as C from '@/constants' import * as Kb from '@/common-adapters' import {UpdatePassword} from './password' -import {usePWState} from '@/constants/settings-password' -import {useSettingsState} from '@/constants/settings' -import {useLogoutState} from '@/constants/logout' +import {usePWState} from '@/stores/settings-password' +import {useSettingsState} from '@/stores/settings' +import {useLogoutState} from '@/stores/logout' const LogoutContainer = () => { const {checkPassword, checkPasswordIsCorrect, resetCheckPassword} = useSettingsState( diff --git a/shared/settings/manage-contacts.tsx b/shared/settings/manage-contacts.tsx index fa5f9f6a3764..47cd4f558a43 100644 --- a/shared/settings/manage-contacts.tsx +++ b/shared/settings/manage-contacts.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SettingsSection} from './account' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {settingsFeedbackTab} from '@/constants/settings' -import {useConfigState} from '@/constants/config' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {settingsFeedbackTab} from '@/stores/settings' +import {useConfigState} from '@/stores/config' const enabledDescription = 'Your phone contacts are being synced on this device.' const disabledDescription = 'Import your phone contacts and start encrypted chats with your friends.' @@ -72,7 +72,7 @@ const ManageContactsBanner = () => { status: s.permissionStatus, })) ) - const onOpenAppSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenAppSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const {appendNewChatBuilder, navigateAppend, switchTab} = C.useRouterState( C.useShallow(s => ({ appendNewChatBuilder: s.appendNewChatBuilder, diff --git a/shared/settings/notifications/hooks.tsx b/shared/settings/notifications/hooks.tsx index 210c1149d735..a3c0aecc59fd 100644 --- a/shared/settings/notifications/hooks.tsx +++ b/shared/settings/notifications/hooks.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import {useSettingsEmailState} from '@/constants/settings-email' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState, settingsAccountTab} from '@/constants/settings' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState, settingsAccountTab} from '@/stores/settings' const useNotifications = () => { const _groups = useSettingsNotifState(s => s.groups) diff --git a/shared/settings/notifications/index.desktop.tsx b/shared/settings/notifications/index.desktop.tsx index dc0c8c53aa9e..cb3bf4b94e1b 100644 --- a/shared/settings/notifications/index.desktop.tsx +++ b/shared/settings/notifications/index.desktop.tsx @@ -1,8 +1,8 @@ import {Reloadable} from '@/common-adapters' import * as C from '@/constants' import Render from './render' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState} from '@/constants/settings' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState} from '@/stores/settings' const Notifications = () => { const loadSettings = useSettingsState(s => s.dispatch.loadSettings) diff --git a/shared/settings/notifications/index.native.tsx b/shared/settings/notifications/index.native.tsx index f06118f6b422..d7cf7fe2d81a 100644 --- a/shared/settings/notifications/index.native.tsx +++ b/shared/settings/notifications/index.native.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import Notifications from './render' import {Reloadable} from '@/common-adapters' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState} from '@/constants/settings' -import {usePushState} from '@/constants/push' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState} from '@/stores/settings' +import {usePushState} from '@/stores/push' const MobileNotifications = () => { const loadSettings = useSettingsState(s => s.dispatch.loadSettings) diff --git a/shared/settings/notifications/push-prompt.tsx b/shared/settings/notifications/push-prompt.tsx index 9917a9110fc4..1cf01b98196a 100644 --- a/shared/settings/notifications/push-prompt.tsx +++ b/shared/settings/notifications/push-prompt.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' const PushPrompt = () => { const rejectPermissions = usePushState(s => s.dispatch.rejectPermissions) diff --git a/shared/settings/notifications/render.tsx b/shared/settings/notifications/render.tsx index 7784aa673b12..fd67e846c9e0 100644 --- a/shared/settings/notifications/render.tsx +++ b/shared/settings/notifications/render.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import useNotifications from './hooks' import Group from '../group' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' type Props = ReturnType diff --git a/shared/settings/password.tsx b/shared/settings/password.tsx index afea9f8392ed..f9c157c2641b 100644 --- a/shared/settings/password.tsx +++ b/shared/settings/password.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import {usePWState} from '@/constants/settings-password' +import {usePWState} from '@/stores/settings-password' type Props = { error: string diff --git a/shared/settings/proxy.tsx b/shared/settings/proxy.tsx index 06975d317b3d..3239af09aba9 100644 --- a/shared/settings/proxy.tsx +++ b/shared/settings/proxy.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const useConnect = () => { const allowTlsMitmToggle = useSettingsState(s => s.didToggleCertificatePinning) diff --git a/shared/settings/root-desktop-tablet.tsx b/shared/settings/root-desktop-tablet.tsx index 7b29c0799c0d..8c76227cdd56 100644 --- a/shared/settings/root-desktop-tablet.tsx +++ b/shared/settings/root-desktop-tablet.tsx @@ -6,7 +6,7 @@ import LeftNav from './sub-nav/left-nav' import {useNavigationBuilder, TabRouter, createNavigatorFactory} from '@react-navigation/core' import type {TypedNavigator, NavigatorTypeBagBase, StaticConfig} from '@react-navigation/native' import {sharedNewRoutes} from './routes' -import {settingsAccountTab} from '@/constants/settings' +import {settingsAccountTab} from '@/stores/settings' const settingsSubRoutes = { ...sharedNewRoutes, diff --git a/shared/settings/root-phone.tsx b/shared/settings/root-phone.tsx index 0ed12c417a3b..e481ddc5883d 100644 --- a/shared/settings/root-phone.tsx +++ b/shared/settings/root-phone.tsx @@ -1,16 +1,16 @@ import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {keybaseFM} from '@/constants/whats-new' +import {keybaseFM} from '@/stores/whats-new' import SettingsItem from './sub-nav/settings-item' import WhatsNewIcon from '../whats-new/icon' import noop from 'lodash/noop' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import * as Settings from '@/constants/settings' -import {usePushState} from '@/constants/push' -import {useNotifState} from '@/constants/notifications' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import * as Settings from '@/stores/settings' +import {usePushState} from '@/stores/push' +import {useNotifState} from '@/stores/notifications' const PerfRow = () => { const [toSubmit, setToSubmit] = React.useState('') diff --git a/shared/settings/routes.tsx b/shared/settings/routes.tsx index 71bf575d9e67..dead7baabb06 100644 --- a/shared/settings/routes.tsx +++ b/shared/settings/routes.tsx @@ -3,7 +3,7 @@ import * as C from '@/constants' import {newRoutes as devicesRoutes} from '../devices/routes' import {newRoutes as gitRoutes} from '../git/routes' import {newRoutes as walletsRoutes} from '../wallets/routes' -import * as Settings from '@/constants/settings/util' +import * as Settings from '@/constants/settings' const SettingsRootDesktop = React.lazy(async () => import('./root-desktop-tablet')) diff --git a/shared/settings/sub-nav/left-nav.tsx b/shared/settings/sub-nav/left-nav.tsx index 12ecd1a074d8..6e7337a14961 100644 --- a/shared/settings/sub-nav/left-nav.tsx +++ b/shared/settings/sub-nav/left-nav.tsx @@ -3,10 +3,10 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import WhatsNewIcon from '@/whats-new/icon' import SettingsItem from './settings-item' -import {keybaseFM} from '@/constants/whats-new' -import * as Settings from '@/constants/settings' -import {usePushState} from '@/constants/push' -import {useNotifState} from '@/constants/notifications' +import {keybaseFM} from '@/stores/whats-new' +import * as Settings from '@/stores/settings' +import {usePushState} from '@/stores/push' +import {useNotifState} from '@/stores/notifications' type Props = { onClick: (s: string) => void diff --git a/shared/signup/common.tsx b/shared/signup/common.tsx index 3659e14d4c50..a7a4b486b22f 100644 --- a/shared/signup/common.tsx +++ b/shared/signup/common.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import {type Props as ButtonProps} from '@/common-adapters/button' import openURL from '@/util/open-url' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' type InfoIconProps = { invisible?: boolean diff --git a/shared/signup/device-name.tsx b/shared/signup/device-name.tsx index 8abc7478d70d..d53ddce22c04 100644 --- a/shared/signup/device-name.tsx +++ b/shared/signup/device-name.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import {SignupScreen, errorBanner} from './common' -import * as Provision from '@/constants/provision' -import {useSignupState} from '@/constants/signup' +import * as Provision from '@/stores/provision' +import {useSignupState} from '@/stores/signup' const ConnectedEnterDevicename = () => { const error = useSignupState(s => s.devicenameError) diff --git a/shared/signup/email.tsx b/shared/signup/email.tsx index 1593cf89c1e0..b167e9e3db0b 100644 --- a/shared/signup/email.tsx +++ b/shared/signup/email.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen, errorBanner} from './common' -import {useSettingsEmailState} from '@/constants/settings-email' -import {useSignupState} from '@/constants/signup' -import {usePushState} from '@/constants/push' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSignupState} from '@/stores/signup' +import {usePushState} from '@/stores/push' const ConnectedEnterEmail = () => { const _showPushPrompt = usePushState(s => C.isMobile && !s.hasPermissions && s.showPushPrompt) diff --git a/shared/signup/feedback.tsx b/shared/signup/feedback.tsx index 51c2ac8ace23..1bc404e8c6a3 100644 --- a/shared/signup/feedback.tsx +++ b/shared/signup/feedback.tsx @@ -4,7 +4,7 @@ import * as React from 'react' import FeedbackForm from '../settings/feedback/index' import {SignupScreen, errorBanner} from './common' import {useSendFeedback} from '../settings/feedback/shared' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const SignupFeedback = () => { const {error: sendError, sendFeedback: onSendFeedback} = useSendFeedback() diff --git a/shared/signup/phone-number/index.tsx b/shared/signup/phone-number/index.tsx index 9467647febbe..03ef62e70146 100644 --- a/shared/signup/phone-number/index.tsx +++ b/shared/signup/phone-number/index.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen, errorBanner} from '../common' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' type BodyProps = { autoFocus?: boolean diff --git a/shared/signup/phone-number/verify.tsx b/shared/signup/phone-number/verify.tsx index 1e58e3fde7aa..b3368353a527 100644 --- a/shared/signup/phone-number/verify.tsx +++ b/shared/signup/phone-number/verify.tsx @@ -4,7 +4,7 @@ import * as Kb from '@/common-adapters' import {SignupScreen} from '../common' import {e164ToDisplay} from '@/util/phone-numbers' import VerifyBody from './verify-body' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' const Container = () => { const error = useSettingsPhoneState(s => (s.verificationState === 'error' ? s.error : '')) diff --git a/shared/signup/username.tsx b/shared/signup/username.tsx index a2b38f4c9cc8..f3f32f1156b3 100644 --- a/shared/signup/username.tsx +++ b/shared/signup/username.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import {SignupScreen, errorBanner} from './common' -import {useSignupState} from '@/constants/signup' -import {useProvisionState} from '@/constants/provision' +import {useSignupState} from '@/stores/signup' +import {useProvisionState} from '@/stores/provision' const ConnectedEnterUsername = () => { const error = useSignupState(s => s.usernameError) diff --git a/shared/constants/archive/index.tsx b/shared/stores/archive.tsx similarity index 93% rename from shared/constants/archive/index.tsx rename to shared/stores/archive.tsx index 772f60224ffc..f5869166c8d0 100644 --- a/shared/constants/archive/index.tsx +++ b/shared/stores/archive.tsx @@ -1,12 +1,11 @@ -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' +import {ignorePromise} from '@/constants/utils' import * as EngineGen from '@/actions/engine-gen-gen' -import * as FS from '@/constants/fs' +import {pathToRPCPath} from '@/constants/fs' import {formatTimeForPopup} from '@/util/timestamp' import {uint8ArrayToHex} from 'uint8array-extras' -import {storeRegistry} from '../store-registry' -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' type ChatJob = { id: string @@ -89,7 +88,6 @@ export interface State extends Store { onEngineIncomingImpl: (action: EngineGen.Actions) => void resetState: 'default' } - chatIDToDisplayname: (id: string) => string } export const useArchiveState = Z.createZustand((set, get) => { @@ -279,7 +277,7 @@ export const useArchiveState = Z.createZustand((set, get) => { await T.RPCGen.SimpleFSSimpleFSArchiveStartRpcPromise({ archiveJobStartPath: { archiveJobStartPathType: T.RPCGen.ArchiveJobStartPathType.kbfs, - kbfs: FS.pathToRPCPath(path).kbfs, + kbfs: pathToRPCPath(path).kbfs, }, outputPath: outPath, overwriteZip: true, @@ -447,23 +445,6 @@ export const useArchiveState = Z.createZustand((set, get) => { } return { ...initialStore, - chatIDToDisplayname: (conversationIDKey: string) => { - const you = storeRegistry.getState('current-user').username - const cs = storeRegistry.getConvoState(conversationIDKey) - const m = cs.meta - if (m.teamname) { - if (m.channelname) { - return `${m.teamname}#${m.channelname}` - } - return m.teamname - } - - const participants = cs.participants.name - if (participants.length === 1) { - return participants[0] ?? '' - } - return participants.filter(username => username !== you).join(',') - }, dispatch, } }) diff --git a/shared/constants/autoreset/index.tsx b/shared/stores/autoreset.tsx similarity index 85% rename from shared/constants/autoreset/index.tsx rename to shared/stores/autoreset.tsx index b7c0910473b7..5c066b41a4e5 100644 --- a/shared/constants/autoreset/index.tsx +++ b/shared/stores/autoreset.tsx @@ -1,12 +1,11 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import * as S from '../strings' +import {ignorePromise} from '@/constants/utils' +import * as S from '@/constants/strings' import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import logger from '@/logger' import {RPCError} from '@/util/errors' -import {navigateAppend, navUpToScreen} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navUpToScreen} from '@/constants/router2' type Store = T.Immutable<{ active: boolean @@ -33,6 +32,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { cancelReset: () => void + defer: { + onGetRecoverPasswordUsername: () => string + onStartProvision: (username: string, fromReset: boolean) => void + } onEngineIncomingImpl: (action: EngineGen.Actions) => void resetState: 'default' resetAccount: (password?: string) => void @@ -62,7 +65,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { switch (error.code) { case T.RPCGen.StatusCode.scnosession: // We got logged out because we were revoked (which might have been - // becase the reset was completed and this device wasn't notified). + // because the reset was completed and this device wasn't notified). return undefined case T.RPCGen.StatusCode.scnotfound: // "User not in autoreset queue." @@ -82,6 +85,14 @@ export const useAutoResetState = Z.createZustand((set, get) => { } ignorePromise(f()) }, + defer: { + onGetRecoverPasswordUsername: () => { + throw new Error('onGetRecoverPasswordUsername not properly initialized') + }, + onStartProvision: (_username: string, _fromReset: boolean) => { + throw new Error('onStartProvision not properly initialized') + }, + }, onEngineIncomingImpl: action => { switch (action.type) { case EngineGen.keybase1NotifyBadgesBadgeState: { @@ -118,7 +129,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { set(s => { s.error = '' }) - storeRegistry.getState('provision').dispatch.startProvision(get().username, true) + get().dispatch.defer.onStartProvision(get().username, true) } else { navUpToScreen('login') } @@ -140,12 +151,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { s.endTime = params.endTime * 1000 }) } - storeRegistry - .getState('router') - .dispatch.navigateAppend( - {props: {pipelineStarted: !params.needVerify}, selected: 'resetWaiting'}, - true - ) + navigateAppend({props: {pipelineStarted: !params.needVerify}, selected: 'resetWaiting'}, true) }, }, params: { @@ -169,7 +175,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { }, resetState: 'default', startAccountReset: (skipPassword, _username) => { - const username = _username || storeRegistry.getState('recover-password').username + const username = _username || get().dispatch.defer.onGetRecoverPasswordUsername() || '' set(s => { s.skipPassword = skipPassword s.error = '' diff --git a/shared/constants/bots/index.tsx b/shared/stores/bots.tsx similarity index 91% rename from shared/constants/bots/index.tsx rename to shared/stores/bots.tsx index beb76b45a4d4..e922a4538206 100644 --- a/shared/constants/bots/index.tsx +++ b/shared/stores/bots.tsx @@ -1,8 +1,8 @@ -import * as T from '../types' +import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import * as Z from '@/util/zustand' -import {ignorePromise, RPCError, isNetworkErr} from '../utils' -import * as S from '../strings' +import {ignorePromise, RPCError, isNetworkErr} from '@/constants/utils' +import * as S from '@/constants/strings' import logger from '@/logger' type BotSearchResults = { @@ -183,4 +183,17 @@ export const useBotsState = Z.createZustand((set, get) => { } }) -export {getFeaturedSorted} from './util' +export const getFeaturedSorted = ( + featuredBotsMap: ReadonlyMap +): Array => { + const featured = [...featuredBotsMap.values()] + featured.sort((a: T.RPCGen.FeaturedBot, b: T.RPCGen.FeaturedBot) => { + if (a.rank < b.rank) { + return 1 + } else if (a.rank > b.rank) { + return -1 + } + return 0 + }) + return featured +} diff --git a/shared/stores/chat2.tsx b/shared/stores/chat2.tsx new file mode 100644 index 000000000000..693304814f4d --- /dev/null +++ b/shared/stores/chat2.tsx @@ -0,0 +1,2047 @@ +import * as Common from '@/constants/chat2/common' +import * as EngineGen from '@/actions/engine-gen-gen' +import * as Message from '@/constants/chat2/message' +import * as Meta from '@/constants/chat2/meta' +import * as S from '@/constants/strings' +import * as T from '@/constants/types' +import * as Tabs from '@/constants/tabs' +import * as TeamConstants from '@/constants/teams' +import * as Z from '@/util/zustand' +import isEqual from 'lodash/isEqual' +import logger from '@/logger' +import type * as Router2 from '@/stores/router2' +import {type ChatProviderProps, ProviderScreen} from '@/stores/convostate' +import type {GetOptionsRet} from '@/constants/types/router2' +import {RPCError} from '@/util/errors' +import {bodyToJSON} from '@/constants/rpc-utils' +import {clearChatStores, chatStores} from '@/stores/convostate' +import {ignorePromise, timeoutPromise, type ViewPropsToPageProps} from '@/constants/utils' +import {isMobile, isPhone} from '@/constants/platform' +import { + navigateAppend, + navUpToScreen, + switchTab, + getModalStack, + getTab, + getVisibleScreen, +} from '@/constants/router2' +import {storeRegistry} from '@/stores/store-registry' +import {uint8ArrayToString} from 'uint8array-extras' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useWaitingState} from '@/stores/waiting' + +const defaultTopReacjis = [ + {name: ':+1:'}, + {name: ':-1:'}, + {name: ':tada:'}, + {name: ':joy:'}, + {name: ':sunglasses:'}, +] +const defaultSkinTone = 1 +const defaultUserReacjis = {skinTone: defaultSkinTone, topReacjis: defaultTopReacjis} + +// while we're debugging chat issues +export const DEBUG_CHAT_DUMP = true + +const blockButtonsGregorPrefix = 'blockButtons.' + +export const inboxSearchMaxTextMessages = 25 +export const inboxSearchMaxTextResults = 50 +export const inboxSearchMaxNameResults = 7 +export const inboxSearchMaxUnreadNameResults = isMobile ? 5 : 10 + +export const makeInboxSearchInfo = (): T.Chat.InboxSearchInfo => ({ + botsResults: [], + botsResultsSuggested: false, + botsStatus: 'initial', + indexPercent: 0, + nameResults: [], + nameResultsUnread: false, + nameStatus: 'initial', + openTeamsResults: [], + openTeamsResultsSuggested: false, + openTeamsStatus: 'initial', + query: '', + selectedIndex: 0, + textResults: [], + textStatus: 'initial', +}) + +const getInboxSearchSelected = ( + inboxSearch: T.Immutable +): + | undefined + | { + conversationIDKey: T.Chat.ConversationIDKey + query?: string + } => { + const {selectedIndex, nameResults, botsResults, openTeamsResults, textResults} = inboxSearch + const firstTextResultIdx = botsResults.length + openTeamsResults.length + nameResults.length + const firstOpenTeamResultIdx = nameResults.length + + if (selectedIndex < firstOpenTeamResultIdx) { + const maybeNameResults = nameResults[selectedIndex] + const conversationIDKey = maybeNameResults === undefined ? undefined : maybeNameResults.conversationIDKey + if (conversationIDKey) { + return { + conversationIDKey, + query: undefined, + } + } + } else if (selectedIndex < firstTextResultIdx) { + return + } else if (selectedIndex >= firstTextResultIdx) { + const result = textResults[selectedIndex - firstTextResultIdx] + if (result) { + return { + conversationIDKey: result.conversationIDKey, + query: result.query, + } + } + } + return +} + +export const getMessageKey = (message: T.Chat.Message) => + `${message.conversationIDKey}:${T.Chat.ordinalToNumber(message.ordinal)}` + +export const getBotsAndParticipants = ( + meta: T.Immutable, + participantInfo: T.Immutable, + sort?: boolean +) => { + const isAdhocTeam = meta.teamType === 'adhoc' + const teamMembers = + useChatState.getState().dispatch.defer.onGetTeamsTeamIDToMembers(meta.teamID) ?? + new Map() + let bots: Array = [] + if (isAdhocTeam) { + bots = participantInfo.all.filter(p => !participantInfo.name.includes(p)) + } else { + bots = [...teamMembers.values()] + .filter( + p => + TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'restrictedbot') || + TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'bot') + ) + .map(p => p.username) + .sort((l, r) => l.localeCompare(r)) + } + let participants: ReadonlyArray = participantInfo.all + if (meta.channelname === 'general') { + participants = [...teamMembers.values()].reduce>((l, mi) => { + l.push(mi.username) + return l + }, []) + } + participants = participants.filter(p => !bots.includes(p)) + participants = sort + ? participants + .map(p => ({ + isAdmin: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'admin') : false, + isOwner: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'owner') : false, + username: p, + })) + .sort((l, r) => { + const leftIsAdmin = l.isAdmin || l.isOwner + const rightIsAdmin = r.isAdmin || r.isOwner + if (leftIsAdmin && !rightIsAdmin) { + return -1 + } else if (!leftIsAdmin && rightIsAdmin) { + return 1 + } + return l.username.localeCompare(r.username) + }) + .map(p => p.username) + : participants + return {bots, participants} +} + +export const getTeamMentionName = (name: string, channel: string) => { + return name + (channel ? `#${channel}` : '') +} + +export const isAssertion = (username: string) => username.includes('@') + +export const clampImageSize = (width: number, height: number, maxWidth: number, maxHeight: number) => { + const aspectRatio = width / height + + let newWidth = width + let newHeight = height + + if (newWidth > maxWidth) { + newWidth = maxWidth + newHeight = newWidth / aspectRatio + } + + if (newHeight > maxHeight) { + newHeight = maxHeight + newWidth = newHeight * aspectRatio + } + + return { + height: Math.ceil(newHeight), + width: Math.ceil(newWidth), + } +} + +export const zoomImage = (width: number, height: number, maxThumbSize: number) => { + const dims = + height > width + ? {height: (maxThumbSize * height) / width, width: maxThumbSize} + : {height: maxThumbSize, width: (maxThumbSize * width) / height} + const marginHeight = dims.height > maxThumbSize ? (dims.height - maxThumbSize) / 2 : 0 + const marginWidth = dims.width > maxThumbSize ? (dims.width - maxThumbSize) / 2 : 0 + return { + dims, + margins: { + marginBottom: -marginHeight, + marginLeft: -marginWidth, + marginRight: -marginWidth, + marginTop: -marginHeight, + }, + } +} + +const uiParticipantsToParticipantInfo = ( + uiParticipants: ReadonlyArray +): T.Chat.ParticipantInfo => { + const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} + uiParticipants.forEach(part => { + const {assertion, contactName, inConvName} = part + participantInfo.all.push(assertion) + if (inConvName) { + participantInfo.name.push(assertion) + } + if (contactName) { + participantInfo.contactName.set(assertion, contactName) + } + }) + return participantInfo +} + +/** + * Returns true if the team is big and you're a member + */ +export const isBigTeam = (state: State, teamID: string): boolean => { + const bigTeams = state.inboxLayout?.bigTeams + return (bigTeams || []).some(v => v.state === T.RPCChat.UIInboxBigTeamRowTyp.label && v.label.id === teamID) +} + +// prettier-ignore +type PreviewReason = + | 'appLink' | 'channelHeader' | 'convertAdHoc' | 'files' | 'forward' | 'fromAReset' + | 'journeyCardPopular' | 'manageView' | 'memberView' | 'messageLink' | 'newChannel' + | 'profile' | 'requestedPayment' | 'resetChatWithoutThem' | 'search' | 'sentPayment' + | 'teamHeader' | 'teamInvite' | 'teamMember' | 'teamMention' | 'teamRow' | 'tracker' | 'transaction' + +type Store = T.Immutable<{ + botPublicCommands: Map + createConversationError?: T.Chat.CreateConversationError + smallTeamBadgeCount: number + bigTeamBadgeCount: number + smallTeamsExpanded: boolean // if we're showing all small teams, + lastCoord?: T.Chat.Coordinate + paymentStatusMap: Map + staticConfig?: T.Chat.StaticConfig // static config stuff from the service. only needs to be loaded once. if null, it hasn't been loaded, + trustedInboxHasLoaded: boolean // if we've done initial trusted inbox load, + userReacjis: T.Chat.UserReacjis + userEmojis?: Array + userEmojisForAutocomplete?: Array + infoPanelShowing: boolean + infoPanelSelectedTab?: 'settings' | 'members' | 'attachments' | 'bots' + inboxNumSmallRows?: number + inboxHasLoaded: boolean // if we've ever loaded, + inboxLayout?: T.RPCChat.UIInboxLayout // layout of the inbox + inboxSearch?: T.Chat.InboxSearchInfo + teamIDToGeneralConvID: Map + flipStatusMap: Map + maybeMentionMap: Map + blockButtonsMap: Map // Should we show block buttons for this team ID? +}> + +const initialStore: Store = { + bigTeamBadgeCount: 0, + blockButtonsMap: new Map(), + botPublicCommands: new Map(), + createConversationError: undefined, + flipStatusMap: new Map(), + inboxHasLoaded: false, + inboxLayout: undefined, + inboxNumSmallRows: 5, + inboxSearch: undefined, + infoPanelSelectedTab: undefined, + infoPanelShowing: false, + lastCoord: undefined, + maybeMentionMap: new Map(), + paymentStatusMap: new Map(), + smallTeamBadgeCount: 0, + smallTeamsExpanded: false, + staticConfig: undefined, + teamIDToGeneralConvID: new Map(), + trustedInboxHasLoaded: false, + userEmojis: undefined, + userEmojisForAutocomplete: undefined, + userReacjis: defaultUserReacjis, +} + +export type RefreshReason = + | 'bootstrap' + | 'componentNeverLoaded' + | 'inboxStale' + | 'inboxSyncedClear' + | 'inboxSyncedUnknown' + | 'joinedAConversation' + | 'leftAConversation' + | 'teamTypeChanged' + | 'maybeKickedFromTeam' + | 'widgetRefresh' + | 'shareConfigSearch' + +export interface State extends Store { + dispatch: { + badgesUpdated: (badgeState?: T.RPCGen.BadgeState) => void + clearMetas: () => void + defer: { + onGetDaemonState: () => {handshakeVersion: number; dispatch: any} + onGetTeamsTeamIDToMembers: ( + teamID: T.Teams.TeamID + ) => ReadonlyMap | undefined + onGetUsersInfoMap: () => ReadonlyMap + onTeamsGetMembers: (teamID: T.Teams.TeamID) => void + onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => void + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => void + } + conversationErrored: ( + allowedUsers: ReadonlyArray, + disallowedUsers: ReadonlyArray, + code: number, + message: string + ) => void + createConversation: (participants: ReadonlyArray, highlightMessageID?: T.Chat.MessageID) => void + ensureWidgetMetas: () => void + findGeneralConvIDFromTeamID: (teamID: T.Teams.TeamID) => void + fetchUserEmoji: (conversationIDKey?: T.Chat.ConversationIDKey, onlyInTeam?: boolean) => void + inboxRefresh: (reason: RefreshReason) => void + inboxSearch: (query: string) => void + inboxSearchMoveSelectedIndex: (increment: boolean) => void + inboxSearchSelect: ( + conversationIDKey?: T.Chat.ConversationIDKey, + query?: string, + selectedIndex?: number + ) => void + loadStaticConfig: () => void + loadedUserEmoji: (results: T.RPCChat.UserEmojiRes) => void + maybeChangeSelectedConv: () => void + messageSendByUsername: (username: string, text: string, waitingKey?: string) => void + metasReceived: ( + metas: ReadonlyArray, + removals?: ReadonlyArray // convs to remove + ) => void + navigateToInbox: (allowSwitchTab?: boolean) => void + onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => void + onEngineIncomingImpl: (action: EngineGen.Actions) => void + onChatInboxSynced: (action: EngineGen.Chat1NotifyChatChatInboxSyncedPayload) => void + onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => void + onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => void + onIncomingInboxUIItem: (inboxUIItem?: T.RPCChat.InboxUIItem) => void + onRouteChanged: (prev: T.Immutable, next: T.Immutable) => void + onTeamBuildingFinished: (users: ReadonlySet) => void + paymentInfoReceived: (paymentInfo: T.Chat.ChatPaymentInfo) => void + previewConversation: (p: { + participants?: ReadonlyArray + teamname?: string + channelname?: string + conversationIDKey?: T.Chat.ConversationIDKey // we only use this when we click on channel mentions. we could maybe change that plumbing but keeping it for now + highlightMessageID?: T.Chat.MessageID + reason: PreviewReason + }) => void + queueMetaToRequest: (ids: ReadonlyArray) => void + queueMetaHandle: () => void + refreshBotPublicCommands: (username: string) => void + resetConversationErrored: () => void + resetState: () => void + setMaybeMentionInfo: (name: string, info: T.RPCChat.UIMaybeMentionInfo) => void + setTrustedInboxHasLoaded: () => void + setInfoPanelTab: (tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void + setInboxNumSmallRows: (rows: number, ignoreWrite?: boolean) => void + toggleInboxSearch: (enabled: boolean) => void + toggleSmallTeamsExpanded: () => void + unboxRows: (ids: ReadonlyArray, force?: boolean) => void + updateCoinFlipStatus: (statuses: ReadonlyArray) => void + updateInboxLayout: (layout: string) => void + updateLastCoord: (coord: T.Chat.Coordinate) => void + updateUserReacjis: (userReacjis: T.RPCGen.UserReacjis) => void + updatedGregor: ( + items: ReadonlyArray<{md: T.RPCGen.Gregor1.Metadata; item: T.RPCGen.Gregor1.Item}> + ) => void + updateInfoPanel: (show: boolean, tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void + } + getBackCount: (conversationIDKey: T.Chat.ConversationIDKey) => number + getBadgeHiddenCount: (ids: ReadonlySet) => { + badgeCount: number + hiddenCount: number + } + getUnreadIndicies: (ids: ReadonlyArray) => Map +} + +// Only get the untrusted conversations out +const untrustedConversationIDKeys = (ids: ReadonlyArray) => + ids.filter(id => storeRegistry.getConvoState(id).meta.trustedState === 'untrusted') + +// generic chat store +export const useChatState = Z.createZustand((set, get) => { + // We keep a set of conversations to unbox + let metaQueue = new Set() + + const dispatch: State['dispatch'] = { + badgesUpdated: b => { + if (!b) return + // clear all first + for (const [, cs] of chatStores) { + cs.getState().dispatch.badgesUpdated(0) + } + b.conversations?.forEach(c => { + const id = T.Chat.conversationIDToKey(c.convID) + storeRegistry.getConvoState(id).dispatch.badgesUpdated(c.badgeCount) + storeRegistry.getConvoState(id).dispatch.unreadUpdated(c.unreadMessages) + }) + const {bigTeamBadgeCount, smallTeamBadgeCount} = b + set(s => { + s.smallTeamBadgeCount = smallTeamBadgeCount + s.bigTeamBadgeCount = bigTeamBadgeCount + }) + }, + clearMetas: () => { + for (const [, cs] of chatStores) { + cs.getState().dispatch.setMeta() + } + }, + conversationErrored: (allowedUsers, disallowedUsers, code, message) => { + set(s => { + s.createConversationError = T.castDraft({ + allowedUsers, + code, + disallowedUsers, + message, + }) + }) + }, + createConversation: (participants, highlightMessageID) => { + // TODO This will break if you try to make 2 new conversations at the same time because there is + // only one pending conversation state. + // The fix involves being able to make multiple pending conversations + const f = async () => { + const username = useCurrentUserState.getState().username + if (!username) { + logger.error('Making a convo while logged out?') + return + } + try { + const result = await T.RPCChat.localNewConversationLocalRpcPromise( + { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + membersType: T.RPCChat.ConversationMembersType.impteamnative, + tlfName: [...new Set([username, ...participants])].join(','), + tlfVisibility: T.RPCGen.TLFVisibility.private, + topicType: T.RPCChat.TopicType.chat, + }, + S.waitingKeyChatCreating + ) + const {conv, uiConv} = result + const conversationIDKey = T.Chat.conversationIDToKey(conv.info.id) + if (!conversationIDKey) { + logger.warn("Couldn't make a new conversation?") + } else { + const meta = Meta.inboxUIItemToConversationMeta(uiConv) + if (meta) { + get().dispatch.metasReceived([meta]) + } + + const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( + uiConv.participants ?? [] + ) + if (participantInfo.all.length > 0) { + storeRegistry + .getConvoState(T.Chat.stringToConversationIDKey(uiConv.convID)) + .dispatch.setParticipants(participantInfo) + } + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.navigateToThread('justCreated', highlightMessageID) + } + } catch (error) { + if (error instanceof RPCError) { + const f = error.fields as Array<{key?: string}> | undefined + const errUsernames = f?.filter(elem => elem.key === 'usernames') as + | undefined + | Array<{key: string; value: string}> + let disallowedUsers: Array = [] + if (errUsernames?.length) { + const {value} = errUsernames[0] ?? {value: ''} + disallowedUsers = value.split(',') + } + const allowedUsers = participants.filter(x => !disallowedUsers.includes(x)) + get().dispatch.conversationErrored(allowedUsers, disallowedUsers, error.code, error.desc) + storeRegistry + .getConvoState(T.Chat.pendingErrorConversationIDKey) + .dispatch.navigateToThread('justCreated', highlightMessageID) + } + } + } + ignorePromise(f()) + }, + defer: { + onGetDaemonState: () => { + throw new Error('onGetDaemonState not properly initialized') + }, + onGetTeamsTeamIDToMembers: (_teamID: T.Teams.TeamID) => { + throw new Error('onGetTeamsTeamIDToMembers not properly initialized') + }, + onGetUsersInfoMap: () => { + throw new Error('onGetUsersInfoMap not properly initialized') + }, + onTeamsGetMembers: (_teamID: T.Teams.TeamID) => { + throw new Error('onTeamsGetMembers not properly initialized') + }, + onTeamsUpdateTeamRetentionPolicy: (_metas: ReadonlyArray) => { + throw new Error('onTeamsUpdateTeamRetentionPolicy not properly initialized') + }, + onUsersUpdates: (_updates: ReadonlyArray<{name: string; info: Partial}>) => { + throw new Error('onUsersUpdates not properly initialized') + }, + }, + ensureWidgetMetas: () => { + const {inboxLayout} = get() + if (!inboxLayout?.widgetList) { + return + } + const missing = inboxLayout.widgetList.reduce>((l, v) => { + if (!storeRegistry.getConvoState(v.convID).isMetaGood()) { + l.push(v.convID) + } + return l + }, []) + if (missing.length === 0) { + return + } + get().dispatch.unboxRows(missing, true) + }, + fetchUserEmoji: (conversationIDKey, onlyInTeam) => { + const f = async () => { + const results = await T.RPCChat.localUserEmojisRpcPromise( + { + convID: + conversationIDKey && conversationIDKey !== T.Chat.noConversationIDKey + ? T.Chat.keyToConversationID(conversationIDKey) + : null, + opts: { + getAliases: true, + getCreationInfo: false, + onlyInTeam: onlyInTeam ?? false, + }, + }, + S.waitingKeyChatLoadingEmoji + ) + get().dispatch.loadedUserEmoji(results) + } + ignorePromise(f()) + }, + findGeneralConvIDFromTeamID: teamID => { + const f = async () => { + try { + const conv = await T.RPCChat.localFindGeneralConvFromTeamIDRpcPromise({teamID}) + const meta = Meta.inboxUIItemToConversationMeta(conv) + if (!meta) { + logger.info(`findGeneralConvIDFromTeamID: failed to convert to meta`) + return + } + get().dispatch.metasReceived([meta]) + set(s => { + s.teamIDToGeneralConvID.set(teamID, T.Chat.stringToConversationIDKey(conv.convID)) + }) + } catch (error) { + if (error instanceof RPCError) { + logger.info(`findGeneralConvIDFromTeamID: failed to get general conv: ${error.message}`) + } + } + } + ignorePromise(f()) + }, + inboxRefresh: reason => { + const f = async () => { + const {username} = useCurrentUserState.getState() + const {loggedIn} = useConfigState.getState() + if (!loggedIn || !username) { + return + } + const clearExistingMetas = reason === 'inboxSyncedClear' + const clearExistingMessages = reason === 'inboxSyncedClear' + + logger.info(`Inbox refresh due to ${reason}`) + const reselectMode = + get().inboxHasLoaded || isPhone + ? T.RPCChat.InboxLayoutReselectMode.default + : T.RPCChat.InboxLayoutReselectMode.force + await T.RPCChat.localRequestInboxLayoutRpcPromise({reselectMode}) + if (clearExistingMetas) { + get().dispatch.clearMetas() + } + if (clearExistingMessages) { + for (const [, cs] of chatStores) { + cs.getState().dispatch.messagesClear() + } + } + } + ignorePromise(f()) + }, + inboxSearch: query => { + set(s => { + const {inboxSearch} = s + if (inboxSearch) { + inboxSearch.query = query + } + }) + const f = async () => { + const teamType = (t: T.RPCChat.TeamType) => (t === T.RPCChat.TeamType.complex ? 'big' : 'small') + + const onConvHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchConvHits']['inParam']) => { + const results = (resp.hits.hits || []).reduce>((arr, h) => { + arr.push({ + conversationIDKey: T.Chat.stringToConversationIDKey(h.convID), + name: h.name, + teamType: teamType(h.teamType), + }) + return arr + }, []) + + set(s => { + const unread = resp.hits.unreadMatches + const {inboxSearch} = s + if (inboxSearch?.nameStatus === 'inprogress') { + inboxSearch.nameResults = results + inboxSearch.nameResultsUnread = unread + inboxSearch.nameStatus = 'success' + } + }) + + const missingMetas = results.reduce>((arr, r) => { + if (!storeRegistry.getConvoState(r.conversationIDKey).isMetaGood()) { + arr.push(r.conversationIDKey) + } + return arr + }, []) + if (missingMetas.length > 0) { + get().dispatch.unboxRows(missingMetas, true) + } + } + + const onOpenTeamHits = ( + resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchTeamHits']['inParam'] + ) => { + const results = (resp.hits.hits || []).reduce>((arr, h) => { + const {description, name, memberCount, inTeam} = h + arr.push({ + description: description ?? '', + inTeam, + memberCount, + name, + publicAdmins: [], + }) + return arr + }, []) + const suggested = resp.hits.suggestedMatches + set(s => { + const {inboxSearch} = s + if (inboxSearch?.openTeamsStatus === 'inprogress') { + inboxSearch.openTeamsResultsSuggested = suggested + inboxSearch.openTeamsResults = T.castDraft(results) + inboxSearch.openTeamsStatus = 'success' + } + }) + } + + const onBotsHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchBotHits']['inParam']) => { + const results = resp.hits.hits || [] + const suggested = resp.hits.suggestedMatches + set(s => { + const {inboxSearch} = s + if (inboxSearch?.botsStatus === 'inprogress') { + inboxSearch.botsResultsSuggested = suggested + inboxSearch.botsResults = T.castDraft(results) + inboxSearch.botsStatus = 'success' + } + }) + } + + const onTextHit = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchInboxHit']['inParam']) => { + const {convID, convName, hits, query, teamType: tt, time} = resp.searchHit + + const result = { + conversationIDKey: T.Chat.conversationIDToKey(convID), + name: convName, + numHits: hits?.length ?? 0, + query, + teamType: teamType(tt), + time, + } as const + set(s => { + const {inboxSearch} = s + if (inboxSearch?.textStatus === 'inprogress') { + const {conversationIDKey} = result + const textResults = inboxSearch.textResults.filter( + r => r.conversationIDKey !== conversationIDKey + ) + textResults.push(result) + inboxSearch.textResults = textResults.sort((l, r) => r.time - l.time) + } + }) + + if ( + storeRegistry.getConvoState(result.conversationIDKey).meta.conversationIDKey === + T.Chat.noConversationIDKey + ) { + get().dispatch.unboxRows([result.conversationIDKey], true) + } + } + const onStart = () => { + set(s => { + const {inboxSearch} = s + if (inboxSearch) { + inboxSearch.nameStatus = 'inprogress' + inboxSearch.selectedIndex = 0 + inboxSearch.textResults = [] + inboxSearch.textStatus = 'inprogress' + inboxSearch.openTeamsStatus = 'inprogress' + inboxSearch.botsStatus = 'inprogress' + } + }) + } + const onDone = () => { + set(s => { + const status = 'success' + const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() + s.inboxSearch = T.castDraft(inboxSearch) + inboxSearch.textStatus = status + }) + } + + const onIndexStatus = ( + resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchIndexStatus']['inParam'] + ) => { + const percent = resp.status.percentIndexed + set(s => { + const {inboxSearch} = s + if (inboxSearch?.textStatus === 'inprogress') { + inboxSearch.indexPercent = percent + } + }) + } + + try { + await T.RPCChat.localSearchInboxRpcListener({ + incomingCallMap: { + 'chat.1.chatUi.chatSearchBotHits': onBotsHits, + 'chat.1.chatUi.chatSearchConvHits': onConvHits, + 'chat.1.chatUi.chatSearchInboxDone': onDone, + 'chat.1.chatUi.chatSearchInboxHit': onTextHit, + 'chat.1.chatUi.chatSearchInboxStart': onStart, + 'chat.1.chatUi.chatSearchIndexStatus': onIndexStatus, + 'chat.1.chatUi.chatSearchTeamHits': onOpenTeamHits, + }, + params: { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + namesOnly: false, + opts: { + afterContext: 0, + beforeContext: 0, + isRegex: false, + matchMentions: false, + maxBots: 10, + maxConvsHit: inboxSearchMaxTextResults, + maxConvsSearched: 0, + maxHits: inboxSearchMaxTextMessages, + maxMessages: -1, + maxNameConvs: query.length > 0 ? inboxSearchMaxNameResults : inboxSearchMaxUnreadNameResults, + maxTeams: 10, + reindexMode: T.RPCChat.ReIndexingMode.postsearchSync, + sentAfter: 0, + sentBefore: 0, + sentBy: '', + sentTo: '', + skipBotCache: false, + }, + query, + }, + }) + } catch (error) { + if (error instanceof RPCError) { + if (!(error.code === T.RPCGen.StatusCode.sccanceled)) { + logger.error('search failed: ' + error.message) + set(s => { + const status = 'error' + const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() + s.inboxSearch = T.castDraft(inboxSearch) + inboxSearch.textStatus = status + }) + } + } + } + } + ignorePromise(f()) + }, + inboxSearchMoveSelectedIndex: increment => { + set(s => { + const {inboxSearch} = s + if (inboxSearch) { + const {selectedIndex} = inboxSearch + const totalResults = inboxSearch.nameResults.length + inboxSearch.textResults.length + if (increment && selectedIndex < totalResults - 1) { + inboxSearch.selectedIndex = selectedIndex + 1 + } else if (!increment && selectedIndex > 0) { + inboxSearch.selectedIndex = selectedIndex - 1 + } + } + }) + }, + inboxSearchSelect: (_conversationIDKey, q, selectedIndex) => { + let conversationIDKey = _conversationIDKey + let query = q + set(s => { + const {inboxSearch} = s + if (inboxSearch && selectedIndex !== undefined) { + inboxSearch.selectedIndex = selectedIndex + } + }) + + const {inboxSearch} = get() + if (!inboxSearch) { + return + } + const selected = getInboxSearchSelected(inboxSearch) + if (!conversationIDKey) { + conversationIDKey = selected?.conversationIDKey + } + + if (!conversationIDKey) { + return + } + if (!query) { + query = selected?.query + } + + storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('inboxSearch') + if (query) { + const cs = storeRegistry.getConvoState(conversationIDKey) + cs.dispatch.setThreadSearchQuery(query) + cs.dispatch.toggleThreadSearch(false) + cs.dispatch.threadSearch(query) + } else { + get().dispatch.toggleInboxSearch(false) + } + }, + loadStaticConfig: () => { + if (get().staticConfig) { + return + } + const {handshakeVersion, dispatch} = get().dispatch.defer.onGetDaemonState() + const f = async () => { + const name = 'chat.loadStatic' + dispatch.wait(name, handshakeVersion, true) + try { + const res = await T.RPCChat.localGetStaticConfigRpcPromise() + if (!res.deletableByDeleteHistory) { + logger.error('chat.loadStaticConfig: got no deletableByDeleteHistory in static config') + return + } + const deletableByDeleteHistory = res.deletableByDeleteHistory.reduce>( + (res, type) => { + const ourTypes = Message.serviceMessageTypeToMessageTypes(type) + res.push(...ourTypes) + return res + }, + [] + ) + set(s => { + s.staticConfig = { + builtinCommands: (res.builtinCommands || []).reduce( + (map, c) => { + map[c.typ] = T.castDraft(c.commands) || [] + return map + }, + { + [T.RPCChat.ConversationBuiltinCommandTyp.none]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.adhoc]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.smallteam]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.bigteam]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.bigteamgeneral]: [], + } + ), + deletableByDeleteHistory: new Set(deletableByDeleteHistory), + } + }) + } finally { + dispatch.wait(name, handshakeVersion, false) + } + } + ignorePromise(f()) + }, + loadedUserEmoji: results => { + set(s => { + const newEmojis: Array = [] + results.emojis.emojis?.forEach(group => { + group.emojis?.forEach(e => newEmojis.push(e)) + }) + s.userEmojisForAutocomplete = newEmojis + s.userEmojis = T.castDraft(results.emojis.emojis) ?? [] + }) + }, + maybeChangeSelectedConv: () => { + const {inboxLayout} = get() + const newConvID = inboxLayout?.reselectInfo?.newConvID + const oldConvID = inboxLayout?.reselectInfo?.oldConvID + + const selectedConversation = Common.getSelectedConversation() + + if (!newConvID && !oldConvID) { + return + } + + const existingValid = T.Chat.isValidConversationIDKey(selectedConversation) + // no new id, just take the opportunity to resolve + if (!newConvID) { + if (!existingValid && isPhone) { + logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) + get().dispatch.navigateToInbox(false) + } + return + } + // not matching? + if (selectedConversation !== oldConvID) { + if (!existingValid && isPhone) { + logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) + get().dispatch.navigateToInbox(false) + } + return + } + // matching + if (isPhone) { + // on mobile just head back to the inbox if we have something selected + if (T.Chat.isValidConversationIDKey(selectedConversation)) { + logger.info(`maybeChangeSelectedConv: mobile: navigating up on conv change`) + get().dispatch.navigateToInbox(false) + return + } + logger.info(`maybeChangeSelectedConv: mobile: ignoring conv change, no conv selected`) + return + } else { + logger.info( + `maybeChangeSelectedConv: selecting new conv: new:${newConvID} old:${oldConvID} prevselected ${selectedConversation}` + ) + storeRegistry.getConvoState(newConvID).dispatch.navigateToThread('findNewestConversation') + } + }, + messageSendByUsername: (username, text, waitingKey) => { + const f = async () => { + const tlfName = `${useCurrentUserState.getState().username},${username}` + try { + const result = await T.RPCChat.localNewConversationLocalRpcPromise( + { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + membersType: T.RPCChat.ConversationMembersType.impteamnative, + tlfName, + tlfVisibility: T.RPCGen.TLFVisibility.private, + topicType: T.RPCChat.TopicType.chat, + }, + waitingKey + ) + storeRegistry + .getConvoState(T.Chat.conversationIDToKey(result.conv.info.id)) + .dispatch.sendMessage(text) + } catch (error) { + if (error instanceof RPCError) { + logger.warn('Could not send in messageSendByUsernames', error.message) + } + } + } + ignorePromise(f()) + }, + metasReceived: (metas, removals) => { + removals?.forEach(r => { + storeRegistry.getConvoState(r).dispatch.setMeta() + }) + metas.forEach(m => { + const {meta: oldMeta, dispatch, isMetaGood} = storeRegistry.getConvoState(m.conversationIDKey) + if (isMetaGood()) { + dispatch.updateMeta(Meta.updateMeta(oldMeta, m)) + } else { + dispatch.setMeta(m) + } + }) + + const selectedConversation = Common.getSelectedConversation() + const {isMetaGood, meta} = storeRegistry.getConvoState(selectedConversation) + if (isMetaGood()) { + const {teamID} = meta + if (!get().dispatch.defer.onGetTeamsTeamIDToMembers(teamID) && meta.teamname) { + get().dispatch.defer.onTeamsGetMembers(teamID) + } + } + }, + navigateToInbox: (allowSwitchTab = true) => { + // components can call us during render sometimes so always defer + setTimeout(() => { + navUpToScreen('chatRoot') + if (allowSwitchTab) { + switchTab(Tabs.chatTab) + } + }, 1) + }, + onChatInboxSynced: action => { + const {syncRes} = action.payload.params + const {clear} = useWaitingState.getState().dispatch + const {inboxRefresh} = get().dispatch + clear(S.waitingKeyChatInboxSyncStarted) + + switch (syncRes.syncType) { + // Just clear it all + case T.RPCChat.SyncInboxResType.clear: + inboxRefresh('inboxSyncedClear') + break + // We're up to date + case T.RPCChat.SyncInboxResType.current: + break + // We got some new messages appended + case T.RPCChat.SyncInboxResType.incremental: { + const items = syncRes.incremental.items || [] + const selectedConversation = Common.getSelectedConversation() + let loadMore = false as boolean + const metas = items.reduce>((arr, i) => { + const meta = Meta.unverifiedInboxUIItemToConversationMeta(i.conv) + if (meta) { + arr.push(meta) + if (meta.conversationIDKey === selectedConversation) { + loadMore = true + } + } + return arr + }, []) + if (loadMore) { + storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) + } + const removals = syncRes.incremental.removals?.map(T.Chat.stringToConversationIDKey) + // Update new untrusted + if (metas.length || removals?.length) { + get().dispatch.metasReceived(metas, removals) + } + + get().dispatch.unboxRows( + items.filter(i => i.shouldUnbox).map(i => T.Chat.stringToConversationIDKey(i.conv.convID)), + true + ) + break + } + default: + inboxRefresh('inboxSyncedUnknown') + } + }, + onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => { + const {updates} = action.payload.params + const keys = ['clear', 'newactivity'] as const + if (__DEV__) { + if (keys.length * 2 !== Object.keys(T.RPCChat.StaleUpdateType).length) { + throw new Error('onChatThreadStale invalid enum') + } + } + let loadMore = false as boolean + const selectedConversation = Common.getSelectedConversation() + keys.forEach(key => { + const conversationIDKeys = (updates || []).reduce>((arr, u) => { + const cid = T.Chat.conversationIDToKey(u.convID) + if (u.updateType === T.RPCChat.StaleUpdateType[key]) { + arr.push(cid) + } + // mentioned? + if (cid === selectedConversation) { + loadMore = true + } + return arr + }, []) + // load the inbox instead + if (conversationIDKeys.length > 0) { + logger.info( + `onChatThreadStale: dispatching thread reload actions for ${conversationIDKeys.length} convs of type ${key}` + ) + get().dispatch.unboxRows(conversationIDKeys, true) + if (T.RPCChat.StaleUpdateType[key] === T.RPCChat.StaleUpdateType.clear) { + conversationIDKeys.forEach(convID => storeRegistry.getConvoState(convID).dispatch.messagesClear()) + } + } + }) + if (loadMore) { + storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) + } + }, + onEngineIncomingImpl: action => { + switch (action.type) { + case EngineGen.chat1ChatUiChatInboxFailed: // fallthrough + case EngineGen.chat1NotifyChatChatSetConvSettings: // fallthrough + case EngineGen.chat1NotifyChatChatAttachmentUploadStart: // fallthrough + case EngineGen.chat1NotifyChatChatPromptUnfurl: // fallthrough + case EngineGen.chat1NotifyChatChatPaymentInfo: // fallthrough + case EngineGen.chat1NotifyChatChatRequestInfo: // fallthrough + case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: //fallthrough + case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: //fallthrough + case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: { + const {convID} = action.payload.params + const conversationIDKey = T.Chat.conversationIDToKey(convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) + break + } + case EngineGen.chat1ChatUiChatCommandMarkdown: //fallthrough + case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: // fallthrough + case EngineGen.chat1ChatUiChatCommandStatus: // fallthrough + case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: //fallthrough + case EngineGen.chat1ChatUiChatGiphySearchResults: { + const {convID} = action.payload.params + const conversationIDKey = T.Chat.stringToConversationIDKey(convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) + break + } + case EngineGen.chat1NotifyChatChatParticipantsInfo: { + const {participants: participantMap} = action.payload.params + Object.keys(participantMap ?? {}).forEach(convIDStr => { + const participants = participantMap?.[convIDStr] + const conversationIDKey = T.Chat.stringToConversationIDKey(convIDStr) + if (participants) { + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.setParticipants(uiParticipantsToParticipantInfo(participants)) + } + }) + break + } + case EngineGen.chat1ChatUiChatMaybeMentionUpdate: { + const {teamName, channel, info} = action.payload.params + get().dispatch.setMaybeMentionInfo(getTeamMentionName(teamName, channel), info) + break + } + case EngineGen.chat1NotifyChatChatConvUpdate: { + const {conv} = action.payload.params + if (conv) { + const meta = Meta.inboxUIItemToConversationMeta(conv) + meta && get().dispatch.metasReceived([meta]) + } + break + } + case EngineGen.chat1ChatUiChatCoinFlipStatus: { + const {statuses} = action.payload.params + get().dispatch.updateCoinFlipStatus(statuses || []) + break + } + case EngineGen.chat1NotifyChatChatThreadsStale: + get().dispatch.onChatThreadStale(action) + break + case EngineGen.chat1NotifyChatChatSubteamRename: { + const {convs} = action.payload.params + const conversationIDKeys = (convs ?? []).map(c => T.Chat.stringToConversationIDKey(c.convID)) + get().dispatch.unboxRows(conversationIDKeys, true) + break + } + case EngineGen.chat1NotifyChatChatTLFFinalize: + get().dispatch.unboxRows([T.Chat.conversationIDToKey(action.payload.params.convID)]) + break + case EngineGen.chat1NotifyChatChatIdentifyUpdate: { + // Some participants are broken/fixed now + const {update} = action.payload.params + const usernames = update.CanonicalName.split(',') + const broken = (update.breaks.breaks || []).map(b => b.user.username) + const updates = usernames.map(name => ({info: {broken: broken.includes(name)}, name})) + get().dispatch.defer.onUsersUpdates(updates) + break + } + case EngineGen.chat1ChatUiChatInboxUnverified: + get().dispatch.onGetInboxUnverifiedConvs(action) + break + case EngineGen.chat1NotifyChatChatInboxSyncStarted: + useWaitingState.getState().dispatch.increment(S.waitingKeyChatInboxSyncStarted) + break + + case EngineGen.chat1NotifyChatChatInboxSynced: + get().dispatch.onChatInboxSynced(action) + break + case EngineGen.chat1ChatUiChatInboxLayout: + get().dispatch.updateInboxLayout(action.payload.params.layout) + get().dispatch.maybeChangeSelectedConv() + get().dispatch.ensureWidgetMetas() + break + case EngineGen.chat1NotifyChatChatInboxStale: + get().dispatch.inboxRefresh('inboxStale') + break + case EngineGen.chat1ChatUiChatInboxConversation: + get().dispatch.onGetInboxConvsUnboxed(action) + break + case EngineGen.chat1NotifyChatNewChatActivity: { + const {activity} = action.payload.params + switch (activity.activityType) { + case T.RPCChat.ChatActivityType.incomingMessage: { + const {incomingMessage} = activity + const conversationIDKey = T.Chat.conversationIDToKey(incomingMessage.convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onIncomingMessage(incomingMessage) + get().dispatch.onIncomingInboxUIItem(incomingMessage.conv ?? undefined) + break + } + case T.RPCChat.ChatActivityType.setStatus: + get().dispatch.onIncomingInboxUIItem(activity.setStatus.conv ?? undefined) + break + case T.RPCChat.ChatActivityType.readMessage: + get().dispatch.onIncomingInboxUIItem(activity.readMessage.conv ?? undefined) + break + case T.RPCChat.ChatActivityType.newConversation: + get().dispatch.onIncomingInboxUIItem(activity.newConversation.conv ?? undefined) + break + case T.RPCChat.ChatActivityType.failedMessage: { + const {failedMessage} = activity + get().dispatch.onIncomingInboxUIItem(failedMessage.conv ?? undefined) + const {outboxRecords} = failedMessage + if (!outboxRecords) return + for (const outboxRecord of outboxRecords) { + const s = outboxRecord.state + if (s.state !== T.RPCChat.OutboxStateType.error) return + const {error} = s + const conversationIDKey = T.Chat.conversationIDToKey(outboxRecord.convID) + const outboxID = T.Chat.rpcOutboxIDToOutboxID(outboxRecord.outboxID) + // This is temp until fixed by CORE-7112. We get this error but not the call to let us show the red banner + const reason = Message.rpcErrorToString(error) + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.onMessageErrored(outboxID, reason, error.typ) + + if (error.typ === T.RPCChat.OutboxErrorType.identify) { + // Find out the user who failed identify + const match = error.message.match(/"(.*)"/) + const tempForceRedBox = match?.[1] + if (tempForceRedBox) { + storeRegistry + .getState('users') + .dispatch.updates([{info: {broken: true}, name: tempForceRedBox}]) + } + } + } + break + } + case T.RPCChat.ChatActivityType.membersUpdate: + get().dispatch.unboxRows([T.Chat.conversationIDToKey(activity.membersUpdate.convID)], true) + break + case T.RPCChat.ChatActivityType.setAppNotificationSettings: { + const {setAppNotificationSettings} = activity + const conversationIDKey = T.Chat.conversationIDToKey(setAppNotificationSettings.convID) + const settings = setAppNotificationSettings.settings + const cs = storeRegistry.getConvoState(conversationIDKey) + if (cs.isMetaGood()) { + cs.dispatch.updateMeta(Meta.parseNotificationSettings(settings)) + } + break + } + case T.RPCChat.ChatActivityType.expunge: { + // Get actions to update messagemap / metamap when retention policy expunge happens + const {expunge} = activity + const conversationIDKey = T.Chat.conversationIDToKey(expunge.convID) + const staticConfig = get().staticConfig + // The types here are askew. It confuses frontend MessageType with protocol MessageType. + // Placeholder is an example where it doesn't make sense. + const deletableMessageTypes = staticConfig?.deletableByDeleteHistory || Common.allMessageTypes + storeRegistry.getConvoState(conversationIDKey).dispatch.messagesWereDeleted({ + deletableMessageTypes, + upToMessageID: T.Chat.numberToMessageID(expunge.expunge.upto), + }) + break + } + case T.RPCChat.ChatActivityType.ephemeralPurge: { + const {ephemeralPurge} = activity + // Get actions to update messagemap / metamap when ephemeral messages expire + const conversationIDKey = T.Chat.conversationIDToKey(ephemeralPurge.convID) + const messageIDs = ephemeralPurge.msgs?.reduce>((arr, msg) => { + const msgID = Message.getMessageID(msg) + if (msgID) { + arr.push(msgID) + } + return arr + }, []) + + !!messageIDs && + storeRegistry.getConvoState(conversationIDKey).dispatch.messagesExploded(messageIDs) + break + } + case T.RPCChat.ChatActivityType.reactionUpdate: { + // Get actions to update the messagemap when reactions are updated + const {reactionUpdate} = activity + const conversationIDKey = T.Chat.conversationIDToKey(reactionUpdate.convID) + if (!reactionUpdate.reactionUpdates || reactionUpdate.reactionUpdates.length === 0) { + logger.warn(`Got ReactionUpdateNotif with no reactionUpdates for convID=${conversationIDKey}`) + break + } + const updates = reactionUpdate.reactionUpdates.map(ru => ({ + reactions: Message.reactionMapToReactions(ru.reactions), + targetMsgID: T.Chat.numberToMessageID(ru.targetMsgID), + })) + logger.info(`Got ${updates.length} reaction updates for convID=${conversationIDKey}`) + storeRegistry.getConvoState(conversationIDKey).dispatch.updateReactions(updates) + get().dispatch.updateUserReacjis(reactionUpdate.userReacjis) + break + } + case T.RPCChat.ChatActivityType.messagesUpdated: { + const {messagesUpdated} = activity + const conversationIDKey = T.Chat.conversationIDToKey(messagesUpdated.convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onMessagesUpdated(messagesUpdated) + break + } + default: + } + break + } + case EngineGen.chat1NotifyChatChatTypingUpdate: { + const {typingUpdates} = action.payload.params + typingUpdates?.forEach(u => { + storeRegistry + .getConvoState(T.Chat.conversationIDToKey(u.convID)) + .dispatch.setTyping(new Set(u.typers?.map(t => t.username))) + }) + break + } + case EngineGen.chat1NotifyChatChatSetConvRetention: { + const {conv, convID} = action.payload.params + if (!conv) { + logger.warn('onChatSetConvRetention: no conv given') + return + } + const meta = Meta.inboxUIItemToConversationMeta(conv) + if (!meta) { + logger.warn(`onChatSetConvRetention: no meta found for ${convID.toString()}`) + return + } + const cs = storeRegistry.getConvoState(meta.conversationIDKey) + // only insert if the convo is already in the inbox + if (cs.isMetaGood()) { + cs.dispatch.setMeta(meta) + } + break + } + case EngineGen.chat1NotifyChatChatSetTeamRetention: { + const {convs} = action.payload.params + const metas = (convs ?? []).reduce>((l, c) => { + const meta = Meta.inboxUIItemToConversationMeta(c) + if (meta) { + l.push(meta) + } + return l + }, []) + if (metas.length) { + metas.forEach(meta => { + const cs = storeRegistry.getConvoState(meta.conversationIDKey) + // only insert if the convo is already in the inbox + if (cs.isMetaGood()) { + cs.dispatch.setMeta(meta) + } + }) + get().dispatch.defer.onTeamsUpdateTeamRetentionPolicy(metas) + } + // this is a more serious problem, but we don't need to bug the user about it + logger.error( + 'got NotifyChat.ChatSetTeamRetention with no attached InboxUIItems. The local version may be out of date' + ) + break + } + case EngineGen.keybase1NotifyBadgesBadgeState: { + const {badgeState} = action.payload.params + get().dispatch.badgesUpdated(badgeState) + break + } + case EngineGen.keybase1GregorUIPushState: { + const {state} = action.payload.params + const items = state.items || [] + const goodState = items.reduce>( + (arr, {md, item}) => { + md && item && arr.push({item, md}) + return arr + }, + [] + ) + if (goodState.length !== items.length) { + logger.warn('Lost some messages in filtering out nonNull gregor items') + } + get().dispatch.updatedGregor(goodState) + break + } + default: + } + }, + onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => { + // TODO not reactive + const infoMap = get().dispatch.defer.onGetUsersInfoMap() + const {convs} = action.payload.params + const inboxUIItems = JSON.parse(convs) as Array + const metas: Array = [] + let added = false as boolean + const usernameToFullname: {[username: string]: string} = {} + inboxUIItems.forEach(inboxUIItem => { + const meta = Meta.inboxUIItemToConversationMeta(inboxUIItem) + if (meta) { + metas.push(meta) + } + const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( + inboxUIItem.participants ?? [] + ) + if (participantInfo.all.length > 0) { + storeRegistry + .getConvoState(T.Chat.stringToConversationIDKey(inboxUIItem.convID)) + .dispatch.setParticipants(participantInfo) + } + inboxUIItem.participants?.forEach((part: T.RPCChat.UIParticipant) => { + const {assertion, fullName} = part + if (!infoMap.get(assertion) && fullName) { + added = true + usernameToFullname[assertion] = fullName + } + }) + }) + if (added) { + get().dispatch.defer.onUsersUpdates( + Object.keys(usernameToFullname).map(name => ({ + info: {fullname: usernameToFullname[name]}, + name, + })) + ) + } + if (metas.length > 0) { + get().dispatch.metasReceived(metas) + } + }, + onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => { + const {inbox} = action.payload.params + const result = JSON.parse(inbox) as T.RPCChat.UnverifiedInboxUIItems + const items: ReadonlyArray = result.items ?? [] + // We get a subset of meta information from the cache even in the untrusted payload + const metas = items.reduce>((arr, item) => { + const m = Meta.unverifiedInboxUIItemToConversationMeta(item) + m && arr.push(m) + return arr + }, []) + get().dispatch.setTrustedInboxHasLoaded() + // Check if some of our existing stored metas might no longer be valid + get().dispatch.metasReceived(metas) + }, + onIncomingInboxUIItem: conv => { + if (!conv) return + const meta = Meta.inboxUIItemToConversationMeta(conv) + const usernameToFullname = (conv.participants ?? []).reduce<{[key: string]: string}>((map, part) => { + if (part.fullName) { + map[part.assertion] = part.fullName + } + return map + }, {}) + + get().dispatch.defer.onUsersUpdates( + Object.keys(usernameToFullname).map(name => ({ + info: {fullname: usernameToFullname[name]}, + name, + })) + ) + + if (meta) { + get().dispatch.metasReceived([meta]) + } + }, + onRouteChanged: (prev, next) => { + const maybeChangeChatSelection = () => { + const wasModal = prev && getModalStack(prev).length > 0 + const isModal = next && getModalStack(next).length > 0 + // ignore if changes involve a modal + if (wasModal || isModal) { + return + } + const p = getVisibleScreen(prev) + const n = getVisibleScreen(next) + const wasChat = p?.name === Common.threadRouteName + const isChat = n?.name === Common.threadRouteName + // nothing to do with chat + if (!wasChat && !isChat) { + return + } + const pParams = p?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} + const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} + const wasID = pParams?.conversationIDKey + const isID = nParams?.conversationIDKey + + logger.info('maybeChangeChatSelection ', {isChat, isID, wasChat, wasID}) + + // same? ignore + if (wasChat && isChat && wasID === isID) { + // if we've never loaded anything, keep going so we load it + if (!isID || storeRegistry.getConvoState(isID).loaded) { + return + } + } + + // deselect if there was one + const deselectAction = () => { + if (wasChat && wasID && T.Chat.isValidConversationIDKey(wasID)) { + get().dispatch.unboxRows([wasID], true) + // needed? + // storeRegistry.getConvoState(wasID).dispatch.clearOrangeLine('deselected') + } + } + + // still chatting? just select new one + if (wasChat && isChat && isID && T.Chat.isValidConversationIDKey(isID)) { + deselectAction() + storeRegistry.getConvoState(isID).dispatch.selectedConversation() + return + } + + // leaving a chat + if (wasChat && !isChat) { + deselectAction() + return + } + + // going into a chat + if (isChat && isID && T.Chat.isValidConversationIDKey(isID)) { + deselectAction() + storeRegistry.getConvoState(isID).dispatch.selectedConversation() + return + } + } + + const maybeChatTabSelected = () => { + if (getTab(prev) !== Tabs.chatTab && getTab(next) === Tabs.chatTab) { + const n = getVisibleScreen(next) + const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} + const isID = nParams?.conversationIDKey + isID && storeRegistry.getConvoState(isID).dispatch.tabSelected() + } + } + maybeChangeChatSelection() + maybeChatTabSelected() + }, + onTeamBuildingFinished: users => { + const f = async () => { + // need to let the mdoal hide first else its thrashy + await timeoutPromise(500) + storeRegistry + .getConvoState(T.Chat.pendingWaitingConversationIDKey) + .dispatch.navigateToThread('justCreated') + get().dispatch.createConversation([...users].map(u => u.id)) + } + ignorePromise(f()) + }, + paymentInfoReceived: paymentInfo => { + set(s => { + s.paymentStatusMap.set(paymentInfo.paymentID, paymentInfo) + }) + }, + previewConversation: p => { + // We always make adhoc convos and never preview it + const previewConversationPersonMakesAConversation = () => { + const {participants, teamname, highlightMessageID} = p + if (teamname) return + if (!participants) return + const toFind = [...participants].sort().join(',') + const toFindN = participants.length + for (const cs of chatStores.values()) { + const names = cs.getState().participants.name + if (names.length !== toFindN) continue + const p = [...names].sort().join(',') + if (p === toFind) { + storeRegistry + .getConvoState(cs.getState().id) + .dispatch.navigateToThread('justCreated', highlightMessageID) + return + } + } + + storeRegistry + .getConvoState(T.Chat.pendingWaitingConversationIDKey) + .dispatch.navigateToThread('justCreated') + get().dispatch.createConversation(participants, highlightMessageID) + } + + // We preview channels + const previewConversationTeam = async () => { + const {conversationIDKey, highlightMessageID, teamname, reason} = p + if (conversationIDKey) { + if ( + reason === 'messageLink' || + reason === 'teamMention' || + reason === 'channelHeader' || + reason === 'manageView' + ) { + // Add preview channel to inbox + await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ + convID: T.Chat.keyToConversationID(conversationIDKey), + }) + } + + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.navigateToThread('previewResolved', highlightMessageID) + return + } + + if (!teamname) { + return + } + + const channelname = p.channelname || 'general' + try { + const results = await T.RPCChat.localFindConversationsLocalRpcPromise({ + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + membersType: T.RPCChat.ConversationMembersType.team, + oneChatPerTLF: true, + tlfName: teamname, + topicName: channelname, + topicType: T.RPCChat.TopicType.chat, + visibility: T.RPCGen.TLFVisibility.private, + }) + const resultMetas = (results.uiConversations || []) + .map(row => Meta.inboxUIItemToConversationMeta(row)) + .filter(Boolean) + + const first = resultMetas[0] + if (!first) { + if (p.reason === 'appLink') { + navigateAppend({ + props: { + error: + "We couldn't find this team chat channel. Please check that you're a member of the team and the channel exists.", + }, + selected: 'keybaseLinkError', + }) + return + } else { + return + } + } + + const results2 = await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ + convID: T.Chat.keyToConversationID(first.conversationIDKey), + }) + const meta = Meta.inboxUIItemToConversationMeta(results2.conv) + if (meta) { + get().dispatch.metasReceived([meta]) + } + + storeRegistry + .getConvoState(first.conversationIDKey) + .dispatch.navigateToThread('previewResolved', highlightMessageID) + } catch (error) { + if ( + error instanceof RPCError && + error.code === T.RPCGen.StatusCode.scteamnotfound && + reason === 'appLink' + ) { + navigateAppend({ + props: { + error: + "We couldn't find this team. Please check that you're a member of the team and the channel exists.", + }, + selected: 'keybaseLinkError', + }) + return + } else { + throw error + } + } + } + previewConversationPersonMakesAConversation() + ignorePromise(previewConversationTeam()) + }, + queueMetaHandle: () => { + // Watch the meta queue and take up to 10 items. Choose the last items first since they're likely still visible + const f = async () => { + const maxToUnboxAtATime = 10 + const ar = [...metaQueue] + const maybeUnbox = ar.slice(0, maxToUnboxAtATime) + metaQueue = new Set(ar.slice(maxToUnboxAtATime)) + const conversationIDKeys = untrustedConversationIDKeys(maybeUnbox) + if (conversationIDKeys.length) { + get().dispatch.unboxRows(conversationIDKeys) + } + if (metaQueue.size && conversationIDKeys.length) { + await timeoutPromise(100) + } + if (metaQueue.size) { + get().dispatch.queueMetaHandle() + } + } + ignorePromise(f()) + }, + queueMetaToRequest: ids => { + let added = false as boolean + untrustedConversationIDKeys(ids).forEach(k => { + if (!metaQueue.has(k)) { + added = true + metaQueue.add(k) + } + }) + if (added) { + // only unboxMore if something changed + get().dispatch.queueMetaHandle() + } else { + logger.info('skipping meta queue run, queue unchanged') + } + }, + refreshBotPublicCommands: username => { + set(s => { + s.botPublicCommands.delete(username) + }) + const f = async () => { + let res: T.RPCChat.ListBotCommandsLocalRes | undefined + try { + res = await T.RPCChat.localListPublicBotCommandsLocalRpcPromise({ + username, + }) + } catch (error) { + if (error instanceof RPCError) { + logger.info('refreshBotPublicCommands: failed to get public commands: ' + error.message) + set(s => { + s.botPublicCommands.set(username, {commands: [], loadError: true}) + }) + } + } + const commands = (res?.commands ?? []).reduce>((l, c) => { + l.push(c.name) + return l + }, []) + + set(s => { + s.botPublicCommands.set(username, {commands, loadError: false}) + }) + } + ignorePromise(f()) + }, + resetConversationErrored: () => { + set(s => { + s.createConversationError = undefined + }) + }, + resetState: () => { + set(s => ({ + ...s, + ...initialStore, + dispatch: s.dispatch, + staticConfig: s.staticConfig, + })) + // also blow away convoState + clearChatStores() + }, + setInboxNumSmallRows: (rows, ignoreWrite) => { + set(s => { + if (rows > 0) { + s.inboxNumSmallRows = rows + } + }) + if (ignoreWrite) { + return + } + const {inboxNumSmallRows} = get() + if (inboxNumSmallRows === undefined || inboxNumSmallRows <= 0) { + return + } + const f = async () => { + try { + await T.RPCGen.configGuiSetValueRpcPromise({ + path: 'ui.inboxSmallRows', + value: {i: inboxNumSmallRows, isNull: false}, + }) + } catch {} + } + ignorePromise(f()) + }, + setInfoPanelTab: tab => { + set(s => { + s.infoPanelSelectedTab = tab + }) + }, + setMaybeMentionInfo: (name, info) => { + set(s => { + const {maybeMentionMap} = s + maybeMentionMap.set(name, T.castDraft(info)) + }) + }, + setTrustedInboxHasLoaded: () => { + set(s => { + s.trustedInboxHasLoaded = true + }) + }, + toggleInboxSearch: enabled => { + set(s => { + const {inboxSearch} = s + if (enabled && !inboxSearch) { + s.inboxSearch = T.castDraft(makeInboxSearchInfo()) + } else if (!enabled && inboxSearch) { + s.inboxSearch = undefined + } + }) + const f = async () => { + const {inboxSearch} = get() + if (!inboxSearch) { + await T.RPCChat.localCancelActiveInboxSearchRpcPromise() + return + } + if (inboxSearch.nameStatus === 'initial') { + get().dispatch.inboxSearch('') + } + } + ignorePromise(f()) + }, + toggleSmallTeamsExpanded: () => { + set(s => { + s.smallTeamsExpanded = !s.smallTeamsExpanded + }) + }, + unboxRows: (ids, force) => { + // We want to unbox rows that have scroll into view + const f = async () => { + if (!useConfigState.getState().loggedIn) { + return + } + + // Get valid keys that we aren't already loading or have loaded + const conversationIDKeys = ids.reduce((arr: Array, id) => { + if (id && T.Chat.isValidConversationIDKey(id)) { + const cs = storeRegistry.getConvoState(id) + const trustedState = cs.meta.trustedState + if (force || (trustedState !== 'requesting' && trustedState !== 'trusted')) { + arr.push(id) + cs.dispatch.updateMeta({trustedState: 'requesting'}) + } + } + return arr + }, []) + + if (!conversationIDKeys.length) { + return + } + logger.info( + `unboxRows: unboxing len: ${conversationIDKeys.length} convs: ${conversationIDKeys.join(',')}` + ) + try { + await T.RPCChat.localRequestInboxUnboxRpcPromise({ + convIDs: conversationIDKeys.map(k => T.Chat.keyToConversationID(k)), + }) + } catch (error) { + if (error instanceof RPCError) { + logger.info(`unboxRows: failed ${error.desc}`) + } + } + } + ignorePromise(f()) + }, + updateCoinFlipStatus: statuses => { + set(s => { + const {flipStatusMap} = s + statuses.forEach(status => { + flipStatusMap.set(status.gameID, T.castDraft(status)) + }) + }) + }, + updateInboxLayout: str => { + set(s => { + try { + const {inboxHasLoaded} = s + const _layout = JSON.parse(str) as unknown + if (!_layout || typeof _layout !== 'object') { + console.log('Invalid layout?') + return + } + const layout = _layout as T.RPCChat.UIInboxLayout + + if (!isEqual(s.inboxLayout, layout)) { + s.inboxLayout = T.castDraft(layout) + } + s.inboxHasLoaded = !!layout + if (!inboxHasLoaded) { + // on first layout, initialize any drafts and muted status + // After the first layout, any other updates will come in the form of meta updates. + layout.smallTeams?.forEach(t => { + const cs = storeRegistry.getConvoState(t.convID) + cs.dispatch.updateFromUIInboxLayout(t) + }) + layout.bigTeams?.forEach(t => { + if (t.state === T.RPCChat.UIInboxBigTeamRowTyp.channel) { + const cs = storeRegistry.getConvoState(t.channel.convID) + cs.dispatch.updateFromUIInboxLayout(t.channel) + } + }) + } + } catch (e) { + logger.info('failed to JSON parse inbox layout: ' + e) + } + }) + }, + updateInfoPanel: (show, tab) => { + set(s => { + s.infoPanelShowing = show + s.infoPanelSelectedTab = tab + }) + }, + updateLastCoord: coord => { + set(s => { + s.lastCoord = coord + }) + const f = async () => { + const {accuracy, lat, lon} = coord + await T.RPCChat.localLocationUpdateRpcPromise({coord: {accuracy, lat, lon}}) + } + ignorePromise(f()) + }, + updateUserReacjis: userReacjis => { + set(s => { + const {skinTone, topReacjis} = userReacjis + s.userReacjis.skinTone = skinTone + // filter out non-simple emojis + s.userReacjis.topReacjis = + T.castDraft(topReacjis)?.filter(r => /^:[^:]+:$/.test(r.name)) ?? defaultTopReacjis + }) + }, + updatedGregor: items => { + const explodingItems = items.filter(i => + i.item.category.startsWith(Common.explodingModeGregorKeyPrefix) + ) + if (!explodingItems.length) { + // No conversations have exploding modes, clear out what is set + for (const s of chatStores.values()) { + s.getState().dispatch.setExplodingMode(0, true) + } + } else { + // logger.info('Got push state with some exploding modes') + explodingItems.forEach(i => { + try { + const {category, body} = i.item + const secondsString = uint8ArrayToString(body) + const seconds = parseInt(secondsString, 10) + if (isNaN(seconds)) { + logger.warn(`Got dirty exploding mode ${secondsString} for category ${category}`) + return + } + const _conversationIDKey = category.substring(Common.explodingModeGregorKeyPrefix.length) + const conversationIDKey = T.Chat.stringToConversationIDKey(_conversationIDKey) + storeRegistry.getConvoState(conversationIDKey).dispatch.setExplodingMode(seconds, true) + } catch (e) { + logger.info('Error parsing exploding' + e) + } + }) + } + + set(s => { + const blockButtons = items.some(i => i.item.category.startsWith(blockButtonsGregorPrefix)) + if (blockButtons || s.blockButtonsMap.size > 0) { + const shouldKeepExistingBlockButtons = new Map() + s.blockButtonsMap.forEach((_, teamID: string) => shouldKeepExistingBlockButtons.set(teamID, false)) + items + .filter(i => i.item.category.startsWith(blockButtonsGregorPrefix)) + .forEach(i => { + try { + const teamID = i.item.category.substring(blockButtonsGregorPrefix.length) + if (!s.blockButtonsMap.get(teamID)) { + const body = bodyToJSON(i.item.body) as {adder: string} + const adder = body.adder + s.blockButtonsMap.set(teamID, {adder}) + } else { + shouldKeepExistingBlockButtons.set(teamID, true) + } + } catch (e) { + logger.info('block buttons parse fail', e) + } + }) + shouldKeepExistingBlockButtons.forEach((keep, teamID) => { + if (!keep) { + s.blockButtonsMap.delete(teamID) + } + }) + } + }) + }, + } + return { + ...initialStore, + dispatch, + getBackCount: conversationIDKey => { + let count = 0 + chatStores.forEach(s => { + const {id, badge} = s.getState() + // only show sum of badges that aren't for the current conversation + if (id !== conversationIDKey) { + count += badge + } + }) + return count + }, + getBadgeHiddenCount: ids => { + let badgeCount = 0 + let hiddenCount = 0 + + chatStores.forEach(s => { + const {id, badge} = s.getState() + if (ids.has(id)) { + badgeCount -= badge + hiddenCount -= 1 + } + }) + + return {badgeCount, hiddenCount} + }, + getUnreadIndicies: ids => { + const unreadIndices: Map = new Map() + ids.forEach((cur, idx) => { + Array.from(chatStores.values()).some(s => { + const {id, badge} = s.getState() + if (id === cur && badge > 0) { + unreadIndices.set(idx, badge) + return true + } + return false + }) + }) + return unreadIndices + }, + } +}) + +export function makeChatScreen>( + Component: COM, + options?: { + getOptions?: GetOptionsRet | ((props: ChatProviderProps>) => GetOptionsRet) + skipProvider?: boolean + canBeNullConvoID?: boolean + } +) { + return { + ...options, + screen: function Screen(p: ChatProviderProps>) { + const Comp = Component as any + return options?.skipProvider ? ( + + ) : ( + + + + ) + }, + } +} + +export * from '@/stores/convostate' +export * from '@/constants/chat2/common' +export * from '@/constants/chat2/meta' +export * from '@/constants/chat2/message' + +export { + noConversationIDKey, + pendingWaitingConversationIDKey, + pendingErrorConversationIDKey, + isValidConversationIDKey, + dummyConversationIDKey, +} from '@/constants/types/chat2/common' diff --git a/shared/constants/config/index.tsx b/shared/stores/config.tsx similarity index 67% rename from shared/constants/config/index.tsx rename to shared/stores/config.tsx index 5fb8ffbb8ea9..249d056e4de9 100644 --- a/shared/constants/config/index.tsx +++ b/shared/stores/config.tsx @@ -1,25 +1,24 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise} from '../utils' -import {serverConfigFileName} from '../platform' -import {waitingKeyConfigLogin} from '../strings' +import type * as NetInfo from '@react-native-community/netinfo' +import * as T from '@/constants/types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' +import {waitingKeyConfigLogin} from '@/constants/strings' import * as EngineGen from '@/actions/engine-gen-gen' -import * as RemoteGen from '@/actions/remote-gen' import * as Stats from '@/engine/stats' import * as Z from '@/util/zustand' -import {noConversationIDKey} from '../types/chat2/common' +import {noConversationIDKey} from '@/constants/types/chat2/common' import isEqual from 'lodash/isEqual' import logger from '@/logger' -import type {Tab} from '../tabs' +import type {Tab} from '@/constants/tabs' import {RPCError, convertToError, isEOFError, isErrorTransient, niceError} from '@/util/errors' -import {defaultUseNativeFrame, isMobile} from '../platform' +import {defaultUseNativeFrame, isMobile} from '@/constants/platform' import {type CommonResponseHandler} from '@/engine/types' -import {invalidPasswordErrorString} from './util' -import {navigateAppend, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {getSelectedConversation} from '@/constants/chat2/common' +import {invalidPasswordErrorString} from '@/constants/config' +import {navigateAppend} from '@/constants/router2' + +export type ConnectionType = NetInfo.NetInfoStateType | 'notavailable' type Store = T.Immutable<{ - forceSmallNav: boolean + active: boolean allowAnimatedEmojis: boolean androidShare?: | {type: T.RPCGen.IncomingShareType.file; urls: Array} @@ -28,6 +27,7 @@ type Store = T.Immutable<{ badgeState?: T.RPCGen.BadgeState configuredAccounts: Array defaultUsername: string + forceSmallNav: boolean globalError?: Error | RPCError gregorReachable?: T.RPCGen.Reachable gregorPushState: Array<{md: T.RPCGregor.Metadata; item: T.RPCGregor.Item}> @@ -50,7 +50,7 @@ type Store = T.Immutable<{ | 'reloggedIn' | 'startupOrReloginButNotInARush' mobileAppState: 'active' | 'background' | 'inactive' | 'unknown' - networkStatus?: {online: boolean; type: T.Config.ConnectionType; isInit?: boolean} + networkStatus?: {online: boolean; type: ConnectionType; isInit?: boolean} notifySound: boolean openAtLogin: boolean outOfDate: T.Config.OutOfDate @@ -86,6 +86,7 @@ type Store = T.Immutable<{ }> const initialStore: Store = { + active: true, allowAnimatedEmojis: true, androidShare: undefined, appFocused: true, @@ -146,7 +147,7 @@ const initialStore: Store = { export interface State extends Store { dispatch: { - dynamic: { + defer: { copyToClipboard: (s: string) => void dumpLogsNative?: (reason: string) => Promise onFilePickerError?: (error: Error) => void @@ -163,7 +164,6 @@ export interface State extends Store { changedFocus: (f: boolean) => void checkForUpdate: () => void dumpLogs: (reason: string) => Promise - eventFromRemoteWindows: (action: RemoteGen.Actions) => void filePickerError: (error: Error) => void initAppUpdateLoop: () => void initNotifySound: () => void @@ -177,16 +177,17 @@ export interface State extends Store { setLoginError: (error?: RPCError) => void logoutAndTryToLogInAs: (username: string) => void onEngineConnected: () => void - onEngineDisonnected: () => void onEngineIncoming: (action: EngineGen.Actions) => void - osNetworkStatusChanged: (online: boolean, type: T.Config.ConnectionType, isInit?: boolean) => void + osNetworkStatusChanged: (online: boolean, type: ConnectionType, isInit?: boolean) => void openUnlockFolders: (devices: ReadonlyArray) => void powerMonitorEvent: (event: string) => void resetState: (isDebug?: boolean) => void remoteWindowNeedsProps: (component: string, params: string) => void resetRevokedSelf: () => void - revoke: (deviceName: string) => void + revoke: (deviceName: string, wasCurrentDevice: boolean) => void + refreshAccounts: () => Promise setAccounts: (a: Store['configuredAccounts']) => void + setActive: (a: boolean) => void setAndroidShare: (s: Store['androidShare']) => void setBadgeState: (b: State['badgeState']) => void setDefaultUsername: (u: string) => void @@ -201,8 +202,9 @@ export interface State extends Store { setStartupDetails: (st: Omit) => void setOpenAtLogin: (open: boolean) => void setOutOfDate: (outOfDate: T.Config.OutOfDate) => void - setUserSwitching: (sw: boolean) => void + setUpdating: () => void setUseNativeFrame: (use: boolean) => void + setUserSwitching: (sw: boolean) => void showMain: () => void toggleRuntimeStats: () => void updateGregorCategory: (category: string, body: string, dtime?: {offset: number; time: number}) => void @@ -245,15 +247,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.gregorReachable = r }) - // Re-get info about our account if you log in/we're done handshaking/became reachable - if (r === T.RPCGen.Reachable.yes) { - // not in waiting state - if (storeRegistry.getState('daemon').handshakeWaiters.size === 0) { - ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) - } - } - - storeRegistry.getState('teams').dispatch.eagerLoadTeams() } const setGregorPushState = (state: T.RPCGen.Gregor1.State) => { @@ -276,29 +269,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.allowAnimatedEmojis = allowAnimatedEmojis }) - - const lastSeenItem = goodState.find(i => i.item.category === 'whatsNewLastSeenVersion') - storeRegistry.getState('whats-new').dispatch.updateLastSeen(lastSeenItem) - } - - const updateApp = () => { - const f = async () => { - await T.RPCGen.configStartUpdateIfNeededRpcPromise() - } - ignorePromise(f()) - // * If user choose to update: - // We'd get killed and it doesn't matter what happens here. - // * If user hits "Ignore": - // Note that we ignore the snooze here, so the state shouldn't change, - // and we'd back to where we think we still need an update. So we could - // have just unset the "updating" flag.However, in case server has - // decided to pull out the update between last time we asked the updater - // and now, we'd be in a wrong state if we didn't check with the service. - // Since user has interacted with it, we still ask the service to make - // sure. - set(s => { - s.outOfDate.updating = true - }) } const updateRuntimeStats = (stats?: T.RPCGen.RuntimeStats) => { @@ -320,13 +290,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.appFocused = f }) - - if (!isMobile || !f) { - return - } - const {dispatch} = storeRegistry.getConvoState(getSelectedConversation()) - dispatch.loadMoreMessages({reason: 'foregrounding'}) - dispatch.markThreadAsRead() }, checkForUpdate: () => { const f = async () => { @@ -334,10 +297,7 @@ export const useConfigState = Z.createZustand((set, get) => { } ignorePromise(f()) }, - dumpLogs: async reason => { - await get().dispatch.dynamic.dumpLogsNative?.(reason) - }, - dynamic: { + defer: { copyToClipboard: () => { throw new Error('copyToClipboard not implemented?????') }, @@ -353,156 +313,11 @@ export const useConfigState = Z.createZustand((set, get) => { showMainNative: undefined, showShareActionSheet: undefined, }, - eventFromRemoteWindows: (action: RemoteGen.Actions) => { - switch (action.type) { - case RemoteGen.resetStore: - break - case RemoteGen.openChatFromWidget: { - get().dispatch.showMain() - storeRegistry - .getConvoState(action.payload.conversationIDKey) - .dispatch.navigateToThread('inboxSmall') - break - } - case RemoteGen.inboxRefresh: { - storeRegistry.getState('chat').dispatch.inboxRefresh('widgetRefresh') - break - } - case RemoteGen.engineConnection: { - if (action.payload.connected) { - storeRegistry.getState('engine').dispatch.onEngineConnected() - } else { - storeRegistry.getState('engine').dispatch.onEngineDisconnected() - } - break - } - case RemoteGen.switchTab: { - switchTab(action.payload.tab) - break - } - case RemoteGen.setCriticalUpdate: { - storeRegistry.getState('fs').dispatch.setCriticalUpdate(action.payload.critical) - break - } - case RemoteGen.userFileEditsLoad: { - storeRegistry.getState('fs').dispatch.userFileEditsLoad() - break - } - case RemoteGen.openFilesFromWidget: { - storeRegistry.getState('fs').dispatch.dynamic.openFilesFromWidgetDesktop?.(action.payload.path) - break - } - case RemoteGen.saltpackFileOpen: { - storeRegistry.getState('deeplinks').dispatch.handleSaltPackOpen(action.payload.path) - break - } - case RemoteGen.pinentryOnCancel: { - storeRegistry.getState('pinentry').dispatch.dynamic.onCancel?.() - break - } - case RemoteGen.pinentryOnSubmit: { - storeRegistry.getState('pinentry').dispatch.dynamic.onSubmit?.(action.payload.password) - break - } - case RemoteGen.openPathInSystemFileManager: { - storeRegistry - .getState('fs') - .dispatch.dynamic.openPathInSystemFileManagerDesktop?.(action.payload.path) - break - } - case RemoteGen.unlockFoldersSubmitPaperKey: { - T.RPCGen.loginPaperKeySubmitRpcPromise( - {paperPhrase: action.payload.paperKey}, - 'unlock-folders:waiting' - ) - .then(() => { - get().dispatch.openUnlockFolders([]) - }) - .catch((e: unknown) => { - if (!(e instanceof RPCError)) return - set(s => { - s.unlockFoldersError = e.desc - }) - }) - break - } - case RemoteGen.closeUnlockFolders: { - T.RPCGen.rekeyRekeyStatusFinishRpcPromise() - .then(() => {}) - .catch(() => {}) - get().dispatch.openUnlockFolders([]) - break - } - case RemoteGen.stop: { - storeRegistry.getState('settings').dispatch.stop(action.payload.exitCode) - break - } - case RemoteGen.trackerChangeFollow: { - storeRegistry - .getState('tracker2') - .dispatch.changeFollow(action.payload.guiID, action.payload.follow) - break - } - case RemoteGen.trackerIgnore: { - storeRegistry.getState('tracker2').dispatch.ignore(action.payload.guiID) - break - } - case RemoteGen.trackerCloseTracker: { - storeRegistry.getState('tracker2').dispatch.closeTracker(action.payload.guiID) - break - } - case RemoteGen.trackerLoad: { - storeRegistry.getState('tracker2').dispatch.load(action.payload) - break - } - case RemoteGen.link: - { - const {link} = action.payload - storeRegistry.getState('deeplinks').dispatch.handleAppLink(link) - } - break - case RemoteGen.installerRan: - get().dispatch.installerRan() - break - case RemoteGen.updateNow: - updateApp() - break - case RemoteGen.powerMonitorEvent: - get().dispatch.powerMonitorEvent(action.payload.event) - break - case RemoteGen.showMain: - get().dispatch.showMain() - break - case RemoteGen.dumpLogs: - ignorePromise(get().dispatch.dumpLogs(action.payload.reason)) - break - case RemoteGen.remoteWindowWantsProps: - get().dispatch.remoteWindowNeedsProps(action.payload.component, action.payload.param) - break - case RemoteGen.updateWindowMaxState: - set(s => { - s.windowState.isMaximized = action.payload.max - }) - break - case RemoteGen.updateWindowState: - get().dispatch.updateWindowState(action.payload.windowState) - break - case RemoteGen.updateWindowShown: { - const win = action.payload.component - set(s => { - s.windowShownCount.set(win, (s.windowShownCount.get(win) ?? 0) + 1) - }) - break - } - case RemoteGen.previewConversation: - storeRegistry - .getState('chat') - .dispatch.previewConversation({participants: [action.payload.participant], reason: 'tracker'}) - break - } + dumpLogs: async reason => { + await get().dispatch.defer.dumpLogsNative?.(reason) }, filePickerError: error => { - get().dispatch.dynamic.onFilePickerError?.(error) + get().dispatch.defer.onFilePickerError?.(error) }, initAppUpdateLoop: () => { const f = async () => { @@ -571,8 +386,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.installerRanCount++ }) - - storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() }, loadIsOnline: () => { const f = async () => { @@ -592,59 +405,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.loadOnStartPhase = phase }) - - if (phase === 'startupOrReloginButNotInARush') { - const getFollowerInfo = () => { - const {uid} = storeRegistry.getState('current-user') - logger.info(`getFollowerInfo: init; uid=${uid}`) - if (uid) { - // request follower info in the background - T.RPCGen.configRequestFollowingAndUnverifiedFollowersRpcPromise() - .then(() => {}) - .catch(() => {}) - } - } - - const updateServerConfig = async () => { - if (get().loggedIn) { - try { - await T.RPCGen.configUpdateLastLoggedInAndServerConfigRpcPromise({ - serverConfigPath: serverConfigFileName, - }) - } catch {} - } - } - - const updateTeams = () => { - storeRegistry.getState('teams').dispatch.getTeams() - storeRegistry.getState('teams').dispatch.refreshTeamRoleMap() - } - - const updateSettings = () => { - storeRegistry.getState('settings-contacts').dispatch.loadContactImportEnabled() - } - - const updateChat = async () => { - // On login lets load the untrusted inbox. This helps make some flows easier - if (storeRegistry.getState('current-user').username) { - const {inboxRefresh} = storeRegistry.getState('chat').dispatch - inboxRefresh('bootstrap') - } - try { - const rows = await T.RPCGen.configGuiGetValueRpcPromise({path: 'ui.inboxSmallRows'}) - const ri = rows.i ?? -1 - if (ri > 0) { - storeRegistry.getState('chat').dispatch.setInboxNumSmallRows(ri, true) - } - } catch {} - } - - getFollowerInfo() - ignorePromise(updateServerConfig()) - updateTeams() - updateSettings() - ignorePromise(updateChat()) - } }, login: (username, passphrase) => { const cancelDesc = 'Canceling RPC' @@ -661,7 +421,7 @@ export const useConfigState = Z.createZustand((set, get) => { 'keybase.1.provisionUi.DisplayAndPromptSecret': cancelOnCallback, 'keybase.1.provisionUi.PromptNewDeviceName': (_, response) => { cancelOnCallback(undefined, response) - storeRegistry.getState('provision').dispatch.dynamic.setUsername?.(username) + navigateAppend({props: {username}, selected: 'username'}) }, 'keybase.1.provisionUi.chooseDevice': cancelOnCallback, 'keybase.1.provisionUi.chooseGPGMethod': cancelOnCallback, @@ -729,8 +489,6 @@ export const useConfigState = Z.createZustand((set, get) => { ignorePromise(f()) }, onEngineConnected: () => { - storeRegistry.getState('daemon').dispatch.startHandshake() - // The startReachability RPC call both starts and returns the current // reachability state. Then we'll get updates of changes from this state via reachabilityChanged. // This should be run on app start and service re-connect in case the service somehow crashed or was restarted manually. @@ -755,16 +513,9 @@ export const useConfigState = Z.createZustand((set, get) => { } ignorePromise(registerForGregorNotifications()) - get().dispatch.dynamic.onEngineConnectedDesktop?.() + get().dispatch.defer.onEngineConnectedDesktop?.() get().dispatch.loadOnStart('initialStartupAsEarlyAsPossible') }, - onEngineDisonnected: () => { - const f = async () => { - await logger.dump() - } - ignorePromise(f()) - storeRegistry.getState('daemon').dispatch.setError(new Error('Disconnected')) - }, onEngineIncoming: action => { switch (action.type) { case EngineGen.keybase1GregorUIPushState: { @@ -798,34 +549,6 @@ export const useConfigState = Z.createZustand((set, get) => { } break } - case EngineGen.keybase1NotifyTeamAvatarUpdated: { - const {name} = action.payload.params - storeRegistry.getState('avatar').dispatch.updated(name) - break - } - case EngineGen.keybase1NotifyTrackingTrackingChanged: { - const {isTracking, username} = action.payload.params - storeRegistry.getState('followers').dispatch.updateFollowing(username, isTracking) - break - } - case EngineGen.keybase1NotifyTrackingTrackingInfo: { - const {uid, followers: _newFollowers, followees: _newFollowing} = action.payload.params - if (storeRegistry.getState('current-user').uid !== uid) { - return - } - const newFollowers = new Set(_newFollowers) - const newFollowing = new Set(_newFollowing) - const { - following: oldFollowing, - followers: oldFollowers, - dispatch, - } = storeRegistry.getState('followers') - const following = isEqual(newFollowing, oldFollowing) ? oldFollowing : newFollowing - const followers = isEqual(newFollowers, oldFollowers) ? oldFollowers : newFollowers - dispatch.replace(followers, following) - break - } - case EngineGen.keybase1ReachabilityReachabilityChanged: if (get().loggedIn) { setGregorReachable(action.payload.params.reachability.reachable) @@ -843,7 +566,7 @@ export const useConfigState = Z.createZustand((set, get) => { })) }) }, - osNetworkStatusChanged: (online: boolean, type: T.Config.ConnectionType, isInit?: boolean) => { + osNetworkStatusChanged: (online: boolean, type: ConnectionType, isInit?: boolean) => { const old = get().networkStatus set(s => { if (!s.networkStatus) { @@ -881,6 +604,30 @@ export const useConfigState = Z.createZustand((set, get) => { } ignorePromise(f()) }, + refreshAccounts: async () => { + const defaultUsername = get().defaultUsername + const configuredAccounts = (await T.RPCGen.loginGetConfiguredAccountsRpcPromise()) ?? [] + const {setAccounts, setDefaultUsername} = get().dispatch + + let existingDefaultFound = false as boolean + let currentName = '' + const nextConfiguredAccounts: Array = [] + + configuredAccounts.forEach(account => { + const {username, isCurrent, fullname, hasStoredSecret} = account + if (username === defaultUsername) { + existingDefaultFound = true + } + if (isCurrent) { + currentName = account.username + } + nextConfiguredAccounts.push({fullname, hasStoredSecret, username}) + }) + if (!existingDefaultFound) { + setDefaultUsername(currentName) + } + setAccounts(nextConfiguredAccounts) + }, remoteWindowNeedsProps: (component, params) => { set(s => { const map = s.remoteWindowNeedsProps.get(component) ?? new Map() @@ -909,8 +656,7 @@ export const useConfigState = Z.createZustand((set, get) => { userSwitching: s.userSwitching, })) }, - revoke: name => { - const wasCurrentDevice = storeRegistry.getState('current-user').deviceName === name + revoke: (name, wasCurrentDevice) => { if (wasCurrentDevice) { const {configuredAccounts, defaultUsername} = get() const acc = configuredAccounts.find(n => n.username !== defaultUsername) @@ -920,7 +666,6 @@ export const useConfigState = Z.createZustand((set, get) => { s.justRevokedSelf = name s.revokedTrigger++ }) - storeRegistry.getState('daemon').dispatch.loadDaemonAccounts() } }, setAccounts: a => { @@ -930,6 +675,11 @@ export const useConfigState = Z.createZustand((set, get) => { } }) }, + setActive: a => { + set(s => { + s.active = a + }) + }, setAndroidShare: share => { set(s => { s.androidShare = T.castDraft(share) @@ -1015,11 +765,6 @@ export const useConfigState = Z.createZustand((set, get) => { if (!changed) return - if (loggedIn) { - ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) - } - storeRegistry.getState('daemon').dispatch.loadDaemonAccounts() - const {loadOnStart} = get().dispatch if (loggedIn) { if (!causedByStartup) { @@ -1035,14 +780,6 @@ export const useConfigState = Z.createZustand((set, get) => { } else { Z.resetAllStores() } - - if (loggedIn) { - storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() - } - - if (!causedByStartup) { - ignorePromise(storeRegistry.getState('daemon').dispatch.refreshAccounts()) - } }, setLoginError: error => { set(s => { @@ -1057,9 +794,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.mobileAppState = nextAppState }) - if (nextAppState === 'background' && storeRegistry.getState('chat').inboxSearch) { - storeRegistry.getState('chat').dispatch.toggleInboxSearch(false) - } }, setNotifySound: n => { set(s => { @@ -1099,6 +833,11 @@ export const useConfigState = Z.createZustand((set, get) => { } }) }, + setUpdating: () => { + set(s => { + s.outOfDate.updating = true + }) + }, setUseNativeFrame: use => { set(s => { s.useNativeFrame = use @@ -1119,7 +858,7 @@ export const useConfigState = Z.createZustand((set, get) => { }) }, showMain: () => { - get().dispatch.dynamic.showMainNative?.() + get().dispatch.defer.showMainNative?.() }, toggleRuntimeStats: () => { const f = async () => { diff --git a/shared/constants/chat2/convostate.tsx b/shared/stores/convostate.tsx similarity index 94% rename from shared/constants/chat2/convostate.tsx rename to shared/stores/convostate.tsx index aa73c3798337..372a1e9cb9f1 100644 --- a/shared/constants/chat2/convostate.tsx +++ b/shared/stores/convostate.tsx @@ -1,7 +1,7 @@ // TODO remove useChatNavigateAppend // TODO remove -import * as TeamsUtil from '../teams/util' -import * as PlatformSpecific from '../platform-specific' +import * as TeamsUtil from '@/constants/teams' +import * as PlatformSpecific from '@/util/platform-specific' import { clearModals, navigateAppend, @@ -11,19 +11,19 @@ import { getVisibleScreen, getModalStack, navToThread, -} from '../router2/util' -import {isIOS} from '../platform' -import {updateImmer} from '../utils' -import * as T from '../types' +} from '@/constants/router2' +import {isIOS} from '@/constants/platform' +import {updateImmer} from '@/constants/utils' +import * as T from '@/constants/types' import * as Styles from '@/styles' -import * as Common from './common' -import * as Tabs from '../tabs' +import * as Common from '@/constants/chat2/common' +import * as Tabs from '@/constants/tabs' import * as EngineGen from '@/actions/engine-gen-gen' -import * as Message from './message' -import * as Meta from './meta' +import * as Message from '@/constants/chat2/message' +import * as Meta from '@/constants/chat2/meta' import * as React from 'react' import * as Z from '@/util/zustand' -import {makeActionForOpenPathInFilesTab} from '@/constants/fs/util' +import {navToPath} from '@/constants/fs' import HiddenString from '@/util/hidden-string' import isEqual from 'lodash/isEqual' import logger from '@/logger' @@ -32,21 +32,23 @@ import type {DebouncedFunc} from 'lodash' import {RPCError} from '@/util/errors' import {findLast} from '@/util/arrays' import {mapGetEnsureValue} from '@/util/map' -import {noConversationIDKey} from '../types/chat2/common' +import {noConversationIDKey} from '@/constants/types/chat2/common' import {type StoreApi, type UseBoundStore, useStore} from 'zustand' -import * as Platform from '../platform' +import * as Platform from '@/constants/platform' import KB2 from '@/util/electron' import NotifyPopup from '@/util/notify-popup' import {hexToUint8Array} from 'uint8array-extras' import assign from 'lodash/assign' import {clearChatTimeCache} from '@/util/timestamp' import {registerDebugClear} from '@/util/debug' -import * as Config from '@/constants/config/util' +import * as Config from '@/constants/config' import {isMobile} from '@/constants/platform' -import {enumKeys, ignorePromise, shallowEqual} from '../utils' +import {enumKeys, ignorePromise, shallowEqual} from '@/constants/utils' import * as Strings from '@/constants/strings' -import {storeRegistry} from '../store-registry' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import type {useChatState, RefreshReason} from '@/stores/chat2' const {darwinCopyToChatTempUploadFile} = KB2.functions @@ -223,6 +225,25 @@ export interface ConvoState extends ConvoStore { botCommandsUpdateStatus: (b: T.RPCChat.UIBotCommandsUpdateStatus) => void channelSuggestionsTriggered: () => void clearAttachmentView: () => void + defer: { + chatBlockButtonsMapHas: (teamID: T.RPCGen.TeamID) => boolean + chatInboxLayoutSmallTeamsFirstConvID: () => T.Chat.ConversationIDKey | undefined + chatInboxRefresh: (reason: RefreshReason) => void + chatMetasReceived: (metas: ReadonlyArray) => void + chatNavigateToInbox: () => void + chatPaymentInfoReceived: (messageID: T.Chat.MessageID, paymentInfo: T.Chat.ChatPaymentInfo) => void + chatPreviewConversation: ( + p: Parameters['dispatch']['previewConversation']>[0] + ) => void + chatResetConversationErrored: () => void + chatUnboxRows: (convIDs: ReadonlyArray, force: boolean) => void + chatUpdateInfoPanel: ( + show: boolean, + tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined + ) => void + teamsGetMembers: (teamID: T.RPCGen.TeamID) => void + usersGetBio: (username: string) => void + } dismissBottomBanner: () => void dismissBlockButtons: (teamID: T.RPCGen.TeamID) => void dismissJourneycard: (cardType: T.RPCChat.JourneycardType, ordinal: T.Chat.Ordinal) => void @@ -296,7 +317,6 @@ export interface ConvoState extends ConvoStore { setReplyTo: (o: T.Chat.Ordinal) => void setThreadSearchQuery: (query: string) => void setTyping: DebouncedFunc<(t: Set) => void> - setupSubscriptions: () => void showInfoPanel: (show: boolean, tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void tabSelected: () => void threadSearch: (query: string) => void @@ -372,11 +392,68 @@ type ScrollDirection = 'none' | 'back' | 'forward' export const numMessagesOnInitialLoad = isMobile ? 20 : 100 export const numMessagesOnScrollback = isMobile ? 100 : 100 -const createSlice: Z.ImmerStateCreator = (set, get) => { +const stubDefer: ConvoState['dispatch']['defer'] = { + chatBlockButtonsMapHas: () => { + throw new Error('convostate defer not initialized') + }, + chatInboxLayoutSmallTeamsFirstConvID: () => { + throw new Error('convostate defer not initialized') + }, + chatInboxRefresh: () => { + throw new Error('convostate defer not initialized') + }, + chatMetasReceived: () => { + throw new Error('convostate defer not initialized') + }, + chatNavigateToInbox: () => { + throw new Error('convostate defer not initialized') + }, + chatPaymentInfoReceived: () => { + throw new Error('convostate defer not initialized') + }, + chatPreviewConversation: () => { + throw new Error('convostate defer not initialized') + }, + chatResetConversationErrored: () => { + throw new Error('convostate defer not initialized') + }, + chatUnboxRows: () => { + throw new Error('convostate defer not initialized') + }, + chatUpdateInfoPanel: () => { + throw new Error('convostate defer not initialized') + }, + teamsGetMembers: () => { + throw new Error('convostate defer not initialized') + }, + usersGetBio: () => { + throw new Error('convostate defer not initialized') + }, +} + +let convoDeferImpl: ConvoState['dispatch']['defer'] | undefined + +export const setConvoDefer = (impl: ConvoState['dispatch']['defer']) => { + convoDeferImpl = impl + for (const store of chatStores.values()) { + const s = store.getState() + store.setState({ + ...s, + dispatch: { + ...s.dispatch, + defer: impl, + }, + }) + } +} + +const createSlice = (): Z.ImmerStateCreator => (set, get) => { + const defer = convoDeferImpl ?? stubDefer + const closeBotModal = () => { clearModals() if (get().meta.teamname) { - storeRegistry.getState('teams').dispatch.getMembers(get().meta.teamID) + get().dispatch.defer.teamsGetMembers(get().meta.teamID) } } @@ -493,13 +570,13 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const onClick = () => { - storeRegistry.getState('config').dispatch.showMain() - storeRegistry.getState('chat').dispatch.navigateToInbox() + useConfigState.getState().dispatch.showMain() + get().dispatch.defer.chatNavigateToInbox() get().dispatch.navigateToThread('desktopNotification') } const onClose = () => {} logger.info('invoking NotifyPopup for chat notification') - const sound = storeRegistry.getState('config').notifySound + const sound = useConfigState.getState().notifySound const cleanBody = body.replaceAll(/!>(.*?) = (set, get) => { logger.error(errMsg) throw new Error(errMsg) } - storeRegistry.getState('chat').dispatch.paymentInfoReceived(paymentInfo) + get().dispatch.defer.chatPaymentInfoReceived(T.Chat.numberToMessageID(msgID), paymentInfo) getConvoState(conversationIDKey).dispatch.paymentInfoReceived(msgID, paymentInfo) } @@ -648,7 +725,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const refreshMutualTeamsInConv = () => { const f = async () => { const {id: conversationIDKey} = get() - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const otherParticipants = Meta.getRowParticipants(get().participants, username || '') const results = await T.RPCChat.localGetMutualTeamsLocalRpcPromise( {usernames: otherParticipants}, @@ -834,7 +911,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const onInboxFailed = (convID: Uint8Array, error: T.RPCChat.InboxUIItemError) => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const conversationIDKey = T.Chat.conversationIDToKey(convID) switch (error.typ) { case T.RPCChat.ConversationErrorType.transient: @@ -1069,7 +1146,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } // If there are block buttons on this conversation, clear them. - if (storeRegistry.getState('chat').blockButtonsMap.has(meta.teamID)) { + if (get().dispatch.defer.chatBlockButtonsMapHas(meta.teamID)) { get().dispatch.dismissBlockButtons(meta.teamID) } @@ -1237,8 +1314,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, blockConversation: reportUser => { const f = async () => { - storeRegistry.getState('chat').dispatch.navigateToInbox() - storeRegistry.getState('config').dispatch.dynamic.persistRoute?.(false, false) + get().dispatch.defer.chatNavigateToInbox() + useConfigState.getState().dispatch.defer.persistRoute?.(false, false) await T.RPCChat.localSetConversationStatusLocalRpcPromise({ conversationID: get().getConvID(), identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, @@ -1270,6 +1347,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { s.attachmentViewMap = new Map() }) }, + defer, dismissBlockButtons: teamID => { const f = async () => { try { @@ -1341,7 +1419,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { // Nav to inbox but don't use findNewConversation since changeSelectedConversation // does that with better information. It knows the conversation is hidden even before // that state bounces back. - storeRegistry.getState('chat').dispatch.navigateToInbox() + get().dispatch.defer.chatNavigateToInbox() get().dispatch.showInfoPanel(false, undefined) } @@ -1394,7 +1472,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const params = vs?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} if (params?.conversationIDKey === get().id) { // select a convo - const next = storeRegistry.getState('chat').inboxLayout?.smallTeams?.[0]?.convID + const next = get().dispatch.defer.chatInboxLayoutSmallTeamsFirstConvID() if (next) { getConvoState(next).dispatch.navigateToThread('findNewestConversationFromLayout') } @@ -1419,8 +1497,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { hit: T.RPCChat.MessageTypes['chat.1.chatUi.chatLoadGalleryHit']['inParam'] ) => { const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const m = Message.uiMessageToMessage( conversationIDKey, hit.message, @@ -1564,8 +1642,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { s.loaded = true }) - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const uiMessages = JSON.parse(thread) as T.RPCChat.UIMessages @@ -1659,9 +1737,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { logger.warn(`loadMoreMessages: error: ${error.desc}`) // no longer in team if (error.code === T.RPCGen.StatusCode.scchatnotinteam) { - const {inboxRefresh, navigateToInbox} = storeRegistry.getState('chat').dispatch - inboxRefresh('maybeKickedFromTeam') - navigateToInbox() + get().dispatch.defer.chatInboxRefresh('maybeKickedFromTeam') + get().dispatch.defer.chatNavigateToInbox() } if (error.code !== T.RPCGen.StatusCode.scteamreaderror) { // scteamreaderror = user is not in team. they'll see the rekey screen so don't throw for that @@ -1703,8 +1780,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }) if (result.message) { - const devicename = storeRegistry.getState('current-user').deviceName - const username = storeRegistry.getState('current-user').username + const devicename = useCurrentUserState.getState().deviceName + const username = useCurrentUserState.getState().username const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const goodMessage = Message.uiMessageToMessage( get().id, @@ -1753,7 +1830,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, markTeamAsRead: teamID => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { logger.info('bail on not logged in') return } @@ -1764,7 +1841,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, markThreadAsRead: force => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { logger.info('mark read bail on not logged in') return } @@ -1957,7 +2034,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { logger.warn("messageReplyPrivately: can't find message to reply to", ordinal) return } - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { throw new Error('messageReplyPrivately: making a convo while logged out?') } @@ -1988,7 +2065,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const text = formatTextForQuoting(message.text.stringValue()) getConvoState(newThreadCID).dispatch.injectIntoInput(text) - storeRegistry.getState('chat').dispatch.metasReceived([meta]) + get().dispatch.defer.chatMetasReceived([meta]) getConvoState(newThreadCID).dispatch.navigateToThread('createdMessagePrivately') } ignorePromise(f()) @@ -2133,7 +2210,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { loadMessages() // load meta - storeRegistry.getState('chat').dispatch.unboxRows([get().id], true) + get().dispatch.defer.chatUnboxRows([get().id], true) const updateNav = () => { const reason = _reason @@ -2169,9 +2246,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { clearModals() } - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {conversationIDKey}, selected: Common.threadRouteName}, replace) + navigateAppend({props: {conversationIDKey}, selected: Common.threadRouteName}, replace) } } updateNav() @@ -2249,7 +2324,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, onIncomingMessage: incoming => { const {message: cMsg} = incoming - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username // check for a reaction outbox notification before doing anything if ( cMsg.state === T.RPCChat.MessageUnboxedState.outbox && @@ -2275,7 +2350,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const conversationIDKey = get().id - const devicename = storeRegistry.getState('current-user').deviceName + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) // special case mutations @@ -2331,8 +2406,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, onMessagesUpdated: messagesUpdated => { if (!messagesUpdated.updates) return - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const toAdd = new Array() messagesUpdated.updates.forEach(uimsg => { @@ -2363,7 +2438,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { ? Config.teamFolder(meta.teamname) : Config.privateFolderWithUsers(participantInfo.name) ) - makeActionForOpenPathInFilesTab(path) + navToPath(path) }, paymentInfoReceived: (messageID, paymentInfo) => { set(s => { @@ -2461,7 +2536,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { // remove all bad people const goodParticipants = new Set(participantInfo.all) meta.resetParticipants.forEach(r => goodParticipants.delete(r)) - storeRegistry.getState('chat').dispatch.previewConversation({ + get().dispatch.defer.chatPreviewConversation({ participants: [...goodParticipants], reason: 'resetChatWithoutThem', }) @@ -2493,7 +2568,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const fetchConversationBio = () => { const participantInfo = get().participants - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const otherParticipants = Meta.getRowParticipants(participantInfo, username || '') if (otherParticipants.length === 1) { // we're in a one-on-one convo @@ -2504,7 +2579,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { return } - storeRegistry.getState('users').dispatch.getBio(username) + get().dispatch.defer.usersGetBio(username) } } @@ -2514,19 +2589,19 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { if (isMetaGood()) { const {teamID, teamname} = meta if (teamname) { - storeRegistry.getState('teams').dispatch.getMembers(teamID) + get().dispatch.defer.teamsGetMembers(teamID) } } } ensureSelectedTeamLoaded() const participantInfo = get().participants const force = !get().isMetaGood() || participantInfo.all.length === 0 - storeRegistry.getState('chat').dispatch.unboxRows([conversationIDKey], force) + get().dispatch.defer.chatUnboxRows([conversationIDKey], force) set(s => { s.threadLoadStatus = T.RPCChat.UIChatThreadStatusTyp.none }) fetchConversationBio() - storeRegistry.getState('chat').dispatch.resetConversationErrored() + get().dispatch.defer.chatResetConversationErrored() }, sendAudioRecording: async (path, duration, amps) => { const outboxID = Common.generateOutboxID() @@ -2608,7 +2683,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { let ordinal = T.Chat.numberToOrdinal(0) // Editing last message if (e === 'last') { - const editLastUser = storeRegistry.getState('current-user').username + const editLastUser = useCurrentUserState.getState().username // Editing your last message const ordinals = get().messageOrdinals const found = @@ -2703,7 +2778,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const conversationIDKey = get().id const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { logger.info('mark unread bail on not logged in') return } @@ -2846,9 +2921,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } }) }, 1000), - setupSubscriptions: () => {}, showInfoPanel: (show, tab) => { - storeRegistry.getState('chat').dispatch.updateInfoPanel(show, tab) + get().dispatch.defer.chatUpdateInfoPanel(show, tab) const conversationIDKey = get().id if (Platform.isPhone) { const visibleScreen = getVisibleScreen() @@ -2876,8 +2950,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const f = async () => { const conversationIDKey = get().id const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const onDone = () => { set(s => { s.threadSearchInfo.status = 'done' @@ -3252,10 +3326,9 @@ registerDebugClear(() => { const createConvoStore = (id: T.Chat.ConversationIDKey) => { const existing = chatStores.get(id) if (existing) return existing - const next = Z.createZustand(createSlice) + const next = Z.createZustand(createSlice()) next.setState({id}) chatStores.set(id, next) - next.getState().dispatch.setupSubscriptions() return next } @@ -3330,14 +3403,12 @@ export const ProviderScreen = React.memo(function ProviderScreen(p: { import type {NavigateAppendType} from '@/router-v2/route-params' export const useChatNavigateAppend = () => { - const useRouterState = storeRegistry.getStore('router') - const navigateAppend = useRouterState(s => s.dispatch.navigateAppend) const cid = useChatContext(s => s.id) return React.useCallback( (makePath: (cid: T.Chat.ConversationIDKey) => NavigateAppendType, replace?: boolean) => { navigateAppend(makePath(cid), replace) }, - [cid, navigateAppend] + [cid] ) } diff --git a/shared/constants/crypto/index.tsx b/shared/stores/crypto.tsx similarity index 97% rename from shared/constants/crypto/index.tsx rename to shared/stores/crypto.tsx index c4ee70dcfe73..99fd2fdd3781 100644 --- a/shared/constants/crypto/index.tsx +++ b/shared/stores/crypto.tsx @@ -1,15 +1,15 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import {isMobile} from '../platform' -import {waitingKeyCrypto} from '../strings' +import {ignorePromise} from '@/constants/utils' +import {isMobile} from '@/constants/platform' +import {waitingKeyCrypto} from '@/constants/strings' import HiddenString from '@/util/hidden-string' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import {RPCError} from '@/util/errors' -import {navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {Operations} from './util' -export * from './util' +import {navigateAppend} from '@/constants/router2' +import {useCurrentUserState} from '@/stores/current-user' +import {Operations} from '@/constants/crypto' +export * from '@/constants/crypto' type CommonStore = { bytesComplete: number @@ -214,7 +214,7 @@ export const useCryptoState = Z.createZustand((set, get) => { const encrypt = (destinationDir: string = '') => { const f = async () => { const start = get().encrypt - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const signed = start.options.sign const inputType = start.inputType const input = start.input.stringValue() @@ -349,7 +349,7 @@ export const useCryptoState = Z.createZustand((set, get) => { const output = await (inputType === 'text' ? callText() : callFile()) - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username set(s => { onSuccess(s.sign, s.sign.input.stringValue() === input, '', output, inputType, true, username, '') }) @@ -530,7 +530,7 @@ export const useCryptoState = Z.createZustand((set, get) => { // User set themselves as a recipient, so don't show 'includeSelf' option // However we don't want to set hideIncludeSelf if we are also encrypting to an SBS user (since we must force includeSelf) - const currentUser = storeRegistry.getState('current-user').username + const currentUser = useCurrentUserState.getState().username const {options} = get().encrypt if (usernames.includes(currentUser) && !hasSBS) { get().dispatch.setEncryptOptions(options, true) diff --git a/shared/constants/current-user/index.tsx b/shared/stores/current-user.tsx similarity index 87% rename from shared/constants/current-user/index.tsx rename to shared/stores/current-user.tsx index 8219dbc286f9..aaba7e2cc2cd 100644 --- a/shared/constants/current-user/index.tsx +++ b/shared/stores/current-user.tsx @@ -1,6 +1,7 @@ -import type * as T from '../types' +import type * as T from '@/constants/types' import * as Z from '@/util/zustand' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ deviceID: T.RPCGen.DeviceID deviceName: string diff --git a/shared/constants/daemon/index.tsx b/shared/stores/daemon.tsx similarity index 70% rename from shared/constants/daemon/index.tsx rename to shared/stores/daemon.tsx index 15d3f95b7f9c..340fcd3949e5 100644 --- a/shared/constants/daemon/index.tsx +++ b/shared/stores/daemon.tsx @@ -1,25 +1,26 @@ import logger from '@/logger' -import {ignorePromise} from '../utils' -import * as T from '../types' +import {ignorePromise} from '@/constants/utils' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' -import {maxHandshakeTries} from '../values' +import {maxHandshakeTries} from '@/constants/values' // Load accounts, this call can be slow so we attempt to continue w/o waiting if we determine we're logged in // normally this wouldn't be worth it but this is startup const getAccountsWaitKey = 'config.getAccounts' type Store = T.Immutable<{ + bootstrapStatus?: T.RPCGen.BootstrapStatus error?: Error - handshakeState: T.Config.DaemonHandshakeState handshakeFailedReason: string handshakeRetriesLeft: number + handshakeState: T.Config.DaemonHandshakeState + handshakeVersion: number handshakeWaiters: Map // if we ever restart handshake up this so we can ignore any waiters for old things - handshakeVersion: number }> const initialStore: Store = { + bootstrapStatus: undefined, handshakeFailedReason: '', handshakeRetriesLeft: maxHandshakeTries, handshakeState: 'starting', @@ -29,9 +30,8 @@ const initialStore: Store = { export interface State extends Store { dispatch: { - loadDaemonAccounts: () => void + loadDaemonAccounts: (configuredAccountsLength: number, loggedIn: boolean, refreshAccounts: () => Promise) => void loadDaemonBootstrapStatus: () => Promise - refreshAccounts: () => Promise resetState: () => void setError: (e?: Error) => void setFailed: (r: string) => void @@ -91,7 +91,6 @@ export const useDaemonState = Z.createZustand((set, get) => { // When there are no more waiters, we can show the actual app - let _emitStartupOnLoadDaemonConnectedOnce = false const dispatch: State['dispatch'] = { daemonHandshake: version => { get().dispatch.setState('waitingForWaiters') @@ -109,22 +108,19 @@ export const useDaemonState = Z.createZustand((set, get) => { wait(name, version, true) try { await get().dispatch.loadDaemonBootstrapStatus() - storeRegistry.getState('dark-mode').dispatch.loadDarkPrefs() - storeRegistry.getState('chat').dispatch.loadStaticConfig() } finally { wait(name, version, false) } } ignorePromise(f()) - get().dispatch.loadDaemonAccounts() }, daemonHandshakeDone: () => { get().dispatch.setState('done') }, - loadDaemonAccounts: () => { + loadDaemonAccounts: (configuredAccountsLength: number, loggedIn: boolean, refreshAccounts: () => Promise) => { const f = async () => { const version = get().handshakeVersion - if (storeRegistry.getState('config').configuredAccounts.length) { + if (configuredAccountsLength) { // bail on already loaded return } @@ -133,7 +129,7 @@ export const useDaemonState = Z.createZustand((set, get) => { const handshakeVersion = version // did we beat getBootstrapStatus? - if (!storeRegistry.getState('config').loggedIn) { + if (!loggedIn) { handshakeWait = true } @@ -143,7 +139,7 @@ export const useDaemonState = Z.createZustand((set, get) => { wait(getAccountsWaitKey, handshakeVersion, true) } - await get().dispatch.refreshAccounts() + await refreshAccounts() if (handshakeWait) { // someone dismissed this already? @@ -170,32 +166,22 @@ export const useDaemonState = Z.createZustand((set, get) => { const {wait} = get().dispatch const f = async () => { - const {setBootstrap} = storeRegistry.getState('current-user').dispatch - const {setDefaultUsername} = storeRegistry.getState('config').dispatch const s = await T.RPCGen.configGetBootstrapStatusRpcPromise() - const {userReacjis, deviceName, deviceID, uid, loggedIn, username} = s - setBootstrap({deviceID, deviceName, uid, username}) - if (username) { - setDefaultUsername(username) - } - if (loggedIn) { - storeRegistry.getState('config').dispatch.setUserSwitching(false) - } + set(state => { + state.bootstrapStatus = T.castDraft(s) + }) - logger.info(`[Bootstrap] loggedIn: ${loggedIn ? 1 : 0}`) - storeRegistry.getState('config').dispatch.setLoggedIn(loggedIn, false) - storeRegistry.getState('chat').dispatch.updateUserReacjis(userReacjis) + logger.info(`[Bootstrap] loggedIn: ${s.loggedIn ? 1 : 0}`) // set HTTP srv info if (s.httpSrvInfo) { logger.info(`[Bootstrap] http server: addr: ${s.httpSrvInfo.address} token: ${s.httpSrvInfo.token}`) - storeRegistry.getState('config').dispatch.setHTTPSrvInfo(s.httpSrvInfo.address, s.httpSrvInfo.token) } else { logger.info(`[Bootstrap] http server: no info given`) } // if we're logged in act like getAccounts is done already - if (loggedIn) { + if (s.loggedIn) { const {handshakeWaiters} = get() if (handshakeWaiters.get(getAccountsWaitKey)) { wait(getAccountsWaitKey, version, false) @@ -205,39 +191,6 @@ export const useDaemonState = Z.createZustand((set, get) => { return await f() }, onRestartHandshakeNative: _onRestartHandshakeNative, - refreshAccounts: async () => { - const configuredAccounts = (await T.RPCGen.loginGetConfiguredAccountsRpcPromise()) ?? [] - // already have one? - const {defaultUsername} = storeRegistry.getState('config') - const {setAccounts, setDefaultUsername} = storeRegistry.getState('config').dispatch - - let existingDefaultFound = false as boolean - let currentName = '' - const nextConfiguredAccounts: Array = [] - const usernameToFullname: {[username: string]: string} = {} - - configuredAccounts.forEach(account => { - const {username, isCurrent, fullname, hasStoredSecret} = account - if (username === defaultUsername) { - existingDefaultFound = true - } - if (isCurrent) { - currentName = account.username - } - nextConfiguredAccounts.push({hasStoredSecret, username}) - usernameToFullname[username] = fullname - }) - if (!existingDefaultFound) { - setDefaultUsername(currentName) - } - setAccounts(nextConfiguredAccounts) - storeRegistry.getState('users').dispatch.updates( - Object.keys(usernameToFullname).map(name => ({ - info: {fullname: usernameToFullname[name]}, - name, - })) - ) - }, resetState: () => { set(s => ({ ...s, @@ -268,13 +221,6 @@ export const useDaemonState = Z.createZustand((set, get) => { set(s => { s.handshakeState = ds }) - - if (ds !== 'done') return - - if (!_emitStartupOnLoadDaemonConnectedOnce) { - _emitStartupOnLoadDaemonConnectedOnce = true - storeRegistry.getState('config').dispatch.loadOnStart('connectedToDaemonForFirstTime') - } }, startHandshake: () => { get().dispatch.setError() diff --git a/shared/constants/darkmode/index.tsx b/shared/stores/darkmode.tsx similarity index 95% rename from shared/constants/darkmode/index.tsx rename to shared/stores/darkmode.tsx index f5e240e3a4d3..c616904f1e4e 100644 --- a/shared/constants/darkmode/index.tsx +++ b/shared/stores/darkmode.tsx @@ -1,10 +1,11 @@ -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import {Appearance} from 'react-native' -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' export type DarkModePreference = 'system' | 'alwaysDark' | 'alwaysLight' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ darkModePreference: DarkModePreference systemDarkMode: boolean diff --git a/shared/constants/devices/index.tsx b/shared/stores/devices.tsx similarity index 96% rename from shared/constants/devices/index.tsx rename to shared/stores/devices.tsx index 61608a6b712f..a8404ad69f98 100644 --- a/shared/constants/devices/index.tsx +++ b/shared/stores/devices.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as Z from '@/util/zustand' -import * as S from '../strings' -import {ignorePromise, updateImmerMap} from '../utils' -import * as T from '../types' +import * as S from '@/constants/strings' +import {ignorePromise, updateImmerMap} from '@/constants/utils' +import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import debounce from 'lodash/debounce' diff --git a/shared/constants/followers/index.tsx b/shared/stores/followers.tsx similarity index 92% rename from shared/constants/followers/index.tsx rename to shared/stores/followers.tsx index f7f550f23102..b403722e07af 100644 --- a/shared/constants/followers/index.tsx +++ b/shared/stores/followers.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import * as Z from '@/util/zustand' - +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ followers: Set following: Set diff --git a/shared/constants/fs/index.tsx b/shared/stores/fs.tsx similarity index 68% rename from shared/constants/fs/index.tsx rename to shared/stores/fs.tsx index b5f112cfcbe5..f3bdde88ab18 100644 --- a/shared/constants/fs/index.tsx +++ b/shared/stores/fs.tsx @@ -1,160 +1,32 @@ import * as EngineGen from '@/actions/engine-gen-gen' -import {ignorePromise, timeoutPromise} from '../utils' -import * as S from '../strings' -import {requestPermissionsToWrite} from '../platform-specific' -import * as Tabs from '../tabs' -import * as T from '../types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' +import * as S from '@/constants/strings' +import {requestPermissionsToWrite} from '@/util/platform-specific' +import * as Tabs from '@/constants/tabs' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import NotifyPopup from '@/util/notify-popup' import {RPCError} from '@/util/errors' import logger from '@/logger' -import {isLinux, isMobile} from '../platform' import {tlfToPreferredOrder} from '@/util/kbfs' import isObject from 'lodash/isObject' import isEqual from 'lodash/isEqual' -import {settingsFsTab} from '../settings/util' -import {navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navigateUp} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import * as Constants from '@/constants/fs' -export {makeActionForOpenPathInFilesTab} from './util' +export * from '@/constants/fs' -const subscriptionDeduplicateIntervalSecond = 1 -export const defaultPath = T.FS.stringToPath('/keybase') - -export const rpcFolderTypeToTlfType = (rpcFolderType: T.RPCGen.FolderType) => { - switch (rpcFolderType) { - case T.RPCGen.FolderType.private: - return T.FS.TlfType.Private - case T.RPCGen.FolderType.public: - return T.FS.TlfType.Public - case T.RPCGen.FolderType.team: - return T.FS.TlfType.Team - default: - return null - } -} - -export const rpcConflictStateToConflictState = ( - rpcConflictState?: T.RPCGen.ConflictState -): T.FS.ConflictState => { - if (rpcConflictState) { - if (rpcConflictState.conflictStateType === T.RPCGen.ConflictStateType.normalview) { - const nv = rpcConflictState.normalview - return makeConflictStateNormalView({ - localViewTlfPaths: (nv.localViews || []).reduce>((arr, p) => { - p.PathType === T.RPCGen.PathType.kbfs && arr.push(rpcPathToPath(p.kbfs)) - return arr - }, []), - resolvingConflict: nv.resolvingConflict, - stuckInConflict: nv.stuckInConflict, - }) - } else { - const nv = rpcConflictState.manualresolvinglocalview.normalView - return makeConflictStateManualResolvingLocalView({ - normalViewTlfPath: nv.PathType === T.RPCGen.PathType.kbfs ? rpcPathToPath(nv.kbfs) : defaultPath, - }) - } - } else { - return tlfNormalViewWithNoConflict - } -} - -export const getSyncConfigFromRPC = ( - tlfName: string, - tlfType: T.FS.TlfType, - config?: T.RPCGen.FolderSyncConfig -): T.FS.TlfSyncConfig => { - if (!config) { - return tlfSyncDisabled - } - switch (config.mode) { - case T.RPCGen.FolderSyncMode.disabled: - return tlfSyncDisabled - case T.RPCGen.FolderSyncMode.enabled: - return tlfSyncEnabled - case T.RPCGen.FolderSyncMode.partial: - return makeTlfSyncPartial({ - enabledPaths: config.paths - ? config.paths.map(str => T.FS.getPathFromRelative(tlfName, tlfType, str)) - : [], - }) - default: - return tlfSyncDisabled - } -} - -// See Installer.m: KBExitFuseKextError -export const ExitCodeFuseKextError = 4 -// See Installer.m: KBExitFuseKextPermissionError -export const ExitCodeFuseKextPermissionError = 5 -// See Installer.m: KBExitAuthCanceledError -export const ExitCodeAuthCanceledError = 6 - -export const emptyNewFolder: T.FS.Edit = { - error: undefined, - name: 'New Folder', - originalName: 'New Folder', - parentPath: T.FS.stringToPath('/keybase'), - type: T.FS.EditType.NewFolder, -} - -export const prefetchNotStarted: T.FS.PrefetchNotStarted = { - state: T.FS.PrefetchState.NotStarted, -} - -export const prefetchComplete: T.FS.PrefetchComplete = { - state: T.FS.PrefetchState.Complete, -} - -export const emptyPrefetchInProgress: T.FS.PrefetchInProgress = { - bytesFetched: 0, - bytesTotal: 0, - endEstimate: 0, - startTime: 0, - state: T.FS.PrefetchState.InProgress, -} - -const pathItemMetadataDefault = { - lastModifiedTimestamp: 0, - lastWriter: '', - name: 'unknown', - prefetchStatus: prefetchNotStarted, - size: 0, - writable: false, -} - -export const emptyFolder: T.FS.FolderPathItem = { - ...pathItemMetadataDefault, - children: new Set(), - progress: T.FS.ProgressType.Pending, - type: T.FS.PathType.Folder, -} - -export const emptyFile: T.FS.FilePathItem = { - ...pathItemMetadataDefault, - type: T.FS.PathType.File, -} - -export const emptySymlink: T.FS.SymlinkPathItem = { - ...pathItemMetadataDefault, - linkTarget: '', - type: T.FS.PathType.Symlink, -} - -export const unknownPathItem: T.FS.UnknownPathItem = { - ...pathItemMetadataDefault, - type: T.FS.PathType.Unknown, -} - -export const tlfSyncEnabled: T.FS.TlfSyncEnabled = { +const tlfSyncEnabled: T.FS.TlfSyncEnabled = { mode: T.FS.TlfSyncMode.Enabled, } -export const tlfSyncDisabled: T.FS.TlfSyncDisabled = { +const tlfSyncDisabled: T.FS.TlfSyncDisabled = { mode: T.FS.TlfSyncMode.Disabled, } -export const makeTlfSyncPartial = ({ +const makeTlfSyncPartial = ({ enabledPaths, }: { enabledPaths?: T.FS.TlfSyncPartial['enabledPaths'] @@ -163,7 +35,7 @@ export const makeTlfSyncPartial = ({ mode: T.FS.TlfSyncMode.Partial, }) -export const makeConflictStateNormalView = ({ +const makeConflictStateNormalView = ({ localViewTlfPaths, resolvingConflict, stuckInConflict, @@ -174,16 +46,16 @@ export const makeConflictStateNormalView = ({ type: T.FS.ConflictStateType.NormalView, }) -export const tlfNormalViewWithNoConflict = makeConflictStateNormalView({}) +const tlfNormalViewWithNoConflict = makeConflictStateNormalView({}) -export const makeConflictStateManualResolvingLocalView = ({ +const makeConflictStateManualResolvingLocalView = ({ normalViewTlfPath, }: Partial): T.FS.ConflictStateManualResolvingLocalView => ({ - normalViewTlfPath: normalViewTlfPath || defaultPath, + normalViewTlfPath: normalViewTlfPath || Constants.defaultPath, type: T.FS.ConflictStateType.ManualResolvingLocalView, }) -export const makeTlf = (p: Partial): T.FS.Tlf => { +const makeTlf = (p: Partial): T.FS.Tlf => { const {conflictState, isFavorite, isIgnored, isNew, name, resetParticipants, syncConfig, teamId, tlfMtime} = p return { @@ -204,186 +76,90 @@ export const makeTlf = (p: Partial): T.FS.Tlf => { } } -export const emptySyncingFoldersProgress: T.FS.SyncingFoldersProgress = { - bytesFetched: 0, - bytesTotal: 0, - endEstimate: 0, - start: 0, -} - -export const emptyOverallSyncStatus: T.FS.OverallSyncStatus = { - diskSpaceStatus: T.FS.DiskSpaceStatus.Ok, - showingBanner: false, - syncingFoldersProgress: emptySyncingFoldersProgress, -} - -export const defaultPathUserSetting: T.FS.PathUserSetting = { - sort: T.FS.SortSetting.NameAsc, -} - -export const defaultTlfListPathUserSetting: T.FS.PathUserSetting = { - sort: T.FS.SortSetting.TimeAsc, -} - -export const emptyDownloadState: T.FS.DownloadState = { - canceled: false, - done: false, - endEstimate: 0, - error: '', - localPath: '', - progress: 0, -} - -export const emptyDownloadInfo: T.FS.DownloadInfo = { - filename: '', - isRegularDownload: false, - path: defaultPath, - startTime: 0, -} - -export const emptyPathItemActionMenu: T.FS.PathItemActionMenu = { - downloadID: undefined, - downloadIntent: undefined, - previousView: T.FS.PathItemActionMenuView.Root, - view: T.FS.PathItemActionMenuView.Root, -} - -export const driverStatusUnknown: T.FS.DriverStatusUnknown = { - type: T.FS.DriverStatusType.Unknown, -} as const - -export const emptyDriverStatusEnabled: T.FS.DriverStatusEnabled = { - dokanOutdated: false, - dokanUninstallExecPath: undefined, - isDisabling: false, - type: T.FS.DriverStatusType.Enabled, -} as const - -export const emptyDriverStatusDisabled: T.FS.DriverStatusDisabled = { - isEnabling: false, - kextPermissionError: false, - type: T.FS.DriverStatusType.Disabled, -} as const - -export const defaultDriverStatus: T.FS.DriverStatus = isLinux ? emptyDriverStatusEnabled : driverStatusUnknown - -export const unknownKbfsDaemonStatus: T.FS.KbfsDaemonStatus = { - onlineStatus: T.FS.KbfsDaemonOnlineStatus.Unknown, - rpcStatus: T.FS.KbfsDaemonRpcStatus.Waiting, -} - -export const emptySettings: T.FS.Settings = { - isLoading: false, - loaded: false, - sfmiBannerDismissed: false, - spaceAvailableNotificationThreshold: 0, - syncOnCellular: false, -} - -export const emptyPathInfo: T.FS.PathInfo = { - deeplinkPath: '', - platformAfterMountPath: '', -} - -export const emptyFileContext: T.FS.FileContext = { - contentType: '', - url: '', - viewType: T.RPCGen.GUIViewType.default, +const rpcFolderTypeToTlfType = (rpcFolderType: T.RPCGen.FolderType) => { + switch (rpcFolderType) { + case T.RPCGen.FolderType.private: + return T.FS.TlfType.Private + case T.RPCGen.FolderType.public: + return T.FS.TlfType.Public + case T.RPCGen.FolderType.team: + return T.FS.TlfType.Team + default: + return null + } } -export const getPathItem = ( - pathItems: T.Immutable>, - path: T.Immutable -): T.Immutable => pathItems.get(path) || (unknownPathItem as T.FS.PathItem) +const rpcPathToPath = (rpcPath: T.RPCGen.KBFSPath) => T.FS.pathConcat(Constants.defaultPath, rpcPath.path) -// RPC expects a string that's interpreted as [16]byte on Go side and it has to -// be unique among all ongoing ops at any given time. uuidv1 may exceed 16 -// bytes, so just roll something simple that's seeded with time. -// -// MAX_SAFE_INTEGER after toString(36) is 11 characters, so this should take <= -// 12 chars -const uuidSeed = Date.now().toString(36) + '-' -let counter = 0 -// We have 36^4=1,679,616 of space to work with in order to not exceed 16 -// bytes. -const counterMod = 36 * 36 * 36 * 36 -export const makeUUID = () => { - counter = (counter + 1) % counterMod - return uuidSeed + counter.toString(36) +const pathFromFolderRPC = (folder: T.RPCGen.Folder): T.FS.Path => { + const visibility = T.FS.getVisibilityFromRPCFolderType(folder.folderType) + if (!visibility) return T.FS.stringToPath('') + return T.FS.stringToPath(`/keybase/${visibility}/${folder.name}`) } -export const clientID = makeUUID() -export const pathToRPCPath = ( - path: T.FS.Path -): {PathType: T.RPCGen.PathType.kbfs; kbfs: T.RPCGen.KBFSPath} => ({ - PathType: T.RPCGen.PathType.kbfs, - kbfs: { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.fsGui, - path: T.FS.pathToString(path).substring('/keybase'.length) || '/', - }, -}) - -export const rpcPathToPath = (rpcPath: T.RPCGen.KBFSPath) => T.FS.pathConcat(defaultPath, rpcPath.path) +const folderRPCFromPath = (path: T.FS.Path): T.RPCGen.FolderHandle | undefined => { + const pathElems = T.FS.getPathElements(path) + if (pathElems.length === 0) return undefined -export const pathTypeToTextType = (type: T.FS.PathType) => - type === T.FS.PathType.Folder ? 'BodySemibold' : 'Body' + const visibility = T.FS.getVisibilityFromElems(pathElems) + if (visibility === undefined) return undefined -export const splitTlfIntoUsernames = (tlf: string): ReadonlyArray => - tlf.split(' ')[0]?.replace(/#/g, ',').split(',') ?? [] + const name = T.FS.getPathNameFromElems(pathElems) + if (name === '') return undefined -export const getUsernamesFromPath = (path: T.FS.Path): ReadonlyArray => { - const elems = T.FS.getPathElements(path) - return elems.length < 3 ? [] : splitTlfIntoUsernames(elems[2]!) + return { + created: false, + folderType: T.FS.getRPCFolderTypeFromVisibility(visibility), + name, + } } -export const humanReadableFileSize = (size: number) => { - const kib = 1024 - const mib = kib * kib - const gib = mib * kib - const tib = gib * kib - - if (!size) return '' - if (size >= tib) return `${Math.round(size / tib)} TB` - if (size >= gib) return `${Math.round(size / gib)} GB` - if (size >= mib) return `${Math.round(size / mib)} MB` - if (size >= kib) return `${Math.round(size / kib)} KB` - return `${size} B` +const rpcConflictStateToConflictState = (rpcConflictState?: T.RPCGen.ConflictState): T.FS.ConflictState => { + if (rpcConflictState) { + if (rpcConflictState.conflictStateType === T.RPCGen.ConflictStateType.normalview) { + const nv = rpcConflictState.normalview + return makeConflictStateNormalView({ + localViewTlfPaths: (nv.localViews || []).reduce>((arr, p) => { + p.PathType === T.RPCGen.PathType.kbfs && arr.push(rpcPathToPath(p.kbfs)) + return arr + }, []), + resolvingConflict: nv.resolvingConflict, + stuckInConflict: nv.stuckInConflict, + }) + } else { + const nv = rpcConflictState.manualresolvinglocalview.normalView + return makeConflictStateManualResolvingLocalView({ + normalViewTlfPath: + nv.PathType === T.RPCGen.PathType.kbfs ? rpcPathToPath(nv.kbfs) : Constants.defaultPath, + }) + } + } else { + return tlfNormalViewWithNoConflict + } } -export const downloadIsOngoing = (dlState: T.FS.DownloadState) => - dlState !== emptyDownloadState && !dlState.error && !dlState.done && !dlState.canceled - -export const getDownloadIntent = ( - path: T.FS.Path, - downloads: T.FS.Downloads, - pathItemActionMenu: T.FS.PathItemActionMenu -): T.FS.DownloadIntent | undefined => { - const found = [...downloads.info].find(([_, info]) => info.path === path) - if (!found) { - return undefined - } - const [downloadID] = found - const dlState = downloads.state.get(downloadID) || emptyDownloadState - if (!downloadIsOngoing(dlState)) { - return undefined +const getSyncConfigFromRPC = ( + tlfName: string, + tlfType: T.FS.TlfType, + config?: T.RPCGen.FolderSyncConfig +): T.FS.TlfSyncConfig => { + if (!config) { + return tlfSyncDisabled } - if (pathItemActionMenu.downloadID === downloadID) { - return pathItemActionMenu.downloadIntent + switch (config.mode) { + case T.RPCGen.FolderSyncMode.disabled: + return tlfSyncDisabled + case T.RPCGen.FolderSyncMode.enabled: + return tlfSyncEnabled + case T.RPCGen.FolderSyncMode.partial: + return makeTlfSyncPartial({ + enabledPaths: config.paths + ? config.paths.map(str => T.FS.getPathFromRelative(tlfName, tlfType, str)) + : [], + }) + default: + return tlfSyncDisabled } - return T.FS.DownloadIntent.None -} - -export const emptyTlfUpdate: T.FS.TlfUpdate = { - history: [], - path: T.FS.stringToPath(''), - serverTime: 0, - writer: '', -} - -export const emptyTlfEdit: T.FS.TlfEdit = { - editType: T.FS.FileEditType.Unknown, - filename: '', - serverTime: 0, } const fsNotificationTypeToEditType = ( @@ -403,7 +179,7 @@ const fsNotificationTypeToEditType = ( } } -export const userTlfHistoryRPCToState = ( +const userTlfHistoryRPCToState = ( history: ReadonlyArray ): T.FS.UserTlfUpdates => { let updates: Array = [] @@ -429,567 +205,46 @@ export const userTlfHistoryRPCToState = ( return updates } -export const canSaveMedia = (pathItem: T.FS.PathItem, fileContext: T.FS.FileContext): boolean => { - if (pathItem.type !== T.FS.PathType.File || fileContext === emptyFileContext) { - return false - } - return ( - fileContext.viewType === T.RPCGen.GUIViewType.image || fileContext.viewType === T.RPCGen.GUIViewType.video - ) -} - -export const folderRPCFromPath = (path: T.FS.Path): T.RPCGen.FolderHandle | undefined => { - const pathElems = T.FS.getPathElements(path) - if (pathElems.length === 0) return undefined - - const visibility = T.FS.getVisibilityFromElems(pathElems) - if (visibility === undefined) return undefined - - const name = T.FS.getPathNameFromElems(pathElems) - if (name === '') return undefined - - return { - created: false, - folderType: T.FS.getRPCFolderTypeFromVisibility(visibility), - name, - } -} - -export const pathFromFolderRPC = (folder: T.RPCGen.Folder): T.FS.Path => { - const visibility = T.FS.getVisibilityFromRPCFolderType(folder.folderType) - if (!visibility) return T.FS.stringToPath('') - return T.FS.stringToPath(`/keybase/${visibility}/${folder.name}`) -} +const subscriptionDeduplicateIntervalSecond = 1 -export const showIgnoreFolder = (path: T.FS.Path, username?: string): boolean => { - const elems = T.FS.getPathElements(path) - if (elems.length !== 3) { - return false - } - return ['public', 'private'].includes(elems[1]!) && elems[2]! !== username +// RPC expects a string that's interpreted as [16]byte on Go side and it has to +// be unique among all ongoing ops at any given time. uuidv1 may exceed 16 +// bytes, so just roll something simple that's seeded with time. +// +// MAX_SAFE_INTEGER after toString(36) is 11 characters, so this should take <= +// 12 chars +const uuidSeed = Date.now().toString(36) + '-' +let counter = 0 +// We have 36^4=1,679,616 of space to work with in order to not exceed 16 +// bytes. +const counterMod = 36 * 36 * 36 * 36 +export const makeUUID = () => { + counter = (counter + 1) % counterMod + return uuidSeed + counter.toString(36) } -export const syntheticEventToTargetRect = (evt?: React.SyntheticEvent): DOMRect | undefined => - isMobile ? undefined : evt ? (evt.target as HTMLElement).getBoundingClientRect() : undefined - -export const invalidTokenError = new Error('invalid token') -export const notFoundError = new Error('not found') +export const clientID = makeUUID() export const makeEditID = (): T.FS.EditID => T.FS.stringToEditID(makeUUID()) -export const getTlfListFromType = ( - tlfs: T.Immutable, - tlfType: T.Immutable -): T.Immutable => { - switch (tlfType) { - case T.FS.TlfType.Private: - return tlfs.private - case T.FS.TlfType.Public: - return tlfs.public - case T.FS.TlfType.Team: - return tlfs.team - default: - return new Map() - } -} - -export const computeBadgeNumberForTlfList = (tlfList: T.Immutable): number => - [...tlfList.values()].reduce((accumulator, tlf) => (tlfIsBadged(tlf) ? accumulator + 1 : accumulator), 0) - -export const computeBadgeNumberForAll = (tlfs: T.Immutable): number => - [T.FS.TlfType.Private, T.FS.TlfType.Public, T.FS.TlfType.Team] - .map(tlfType => computeBadgeNumberForTlfList(getTlfListFromType(tlfs, tlfType))) - .reduce((sum, count) => sum + count, 0) - -export const getTlfPath = (path: T.FS.Path): T.FS.Path => { - const elems = T.FS.getPathElements(path) - return elems.length > 2 ? T.FS.pathConcat(T.FS.pathConcat(defaultPath, elems[1]!), elems[2]!) : undefined -} - -export const getTlfListAndTypeFromPath = ( - tlfs: T.Immutable, - path: T.Immutable -): T.Immutable<{ - tlfList: T.FS.TlfList - tlfType: T.FS.TlfType -}> => { - const visibility = T.FS.getPathVisibility(path) - switch (visibility) { - case T.FS.TlfType.Private: - case T.FS.TlfType.Public: - case T.FS.TlfType.Team: { - const tlfType: T.FS.TlfType = visibility - return {tlfList: getTlfListFromType(tlfs, tlfType), tlfType} - } - default: - return {tlfList: new Map(), tlfType: T.FS.TlfType.Private} - } -} - -export const unknownTlf = makeTlf({}) -export const getTlfFromPathInFavoritesOnly = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { - const elems = T.FS.getPathElements(path) - if (elems.length < 3) { - return unknownTlf - } - const {tlfList} = getTlfListAndTypeFromPath(tlfs, path) - return tlfList.get(elems[2]!) || unknownTlf -} - -export const getTlfFromPath = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { - const fromFavorites = getTlfFromPathInFavoritesOnly(tlfs, path) - return fromFavorites !== unknownTlf - ? fromFavorites - : tlfs.additionalTlfs.get(getTlfPath(path)) || unknownTlf -} - -export const getTlfFromTlfs = (tlfs: T.FS.Tlfs, tlfType: T.FS.TlfType, name: string): T.FS.Tlf => { - switch (tlfType) { - case T.FS.TlfType.Private: - return tlfs.private.get(name) || unknownTlf - case T.FS.TlfType.Public: - return tlfs.public.get(name) || unknownTlf - case T.FS.TlfType.Team: - return tlfs.team.get(name) || unknownTlf - default: - return unknownTlf - } -} - -export const tlfTypeAndNameToPath = (tlfType: T.FS.TlfType, name: string): T.FS.Path => - T.FS.stringToPath(`/keybase/${tlfType}/${name}`) - export const resetBannerType = (s: State, path: T.FS.Path): T.FS.ResetBannerType => { - const resetParticipants = getTlfFromPath(s.tlfs, path).resetParticipants + const resetParticipants = Constants.getTlfFromPath(s.tlfs, path).resetParticipants if (resetParticipants.length === 0) { return T.FS.ResetBannerNoOthersType.None } - const you = storeRegistry.getState('current-user').username + const you = useCurrentUserState.getState().username if (resetParticipants.findIndex(username => username === you) >= 0) { return T.FS.ResetBannerNoOthersType.Self } return resetParticipants.length } -export const getUploadedPath = (parentPath: T.FS.Path, localPath: string) => - T.FS.pathConcat(parentPath, T.FS.getLocalPathName(localPath)) - -export const usernameInPath = (username: string, path: T.FS.Path) => { - const elems = T.FS.getPathElements(path) - return elems.length >= 3 && elems[2]!.split(',').includes(username) -} - -export const getUsernamesFromTlfName = (tlfName: string): Array => { - const split = splitTlfIntoReadersAndWriters(tlfName) - return split.writers.concat(split.readers || []) -} - -export const isOfflineUnsynced = ( - daemonStatus: T.FS.KbfsDaemonStatus, - pathItem: T.FS.PathItem, - path: T.FS.Path -) => - daemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline && - T.FS.getPathLevel(path) > 2 && - pathItem.prefetchStatus !== prefetchComplete - -// To make sure we have consistent badging, all badging related stuff should go -// through this function. That is: -// * When calculating number of TLFs being badged, a TLF should be counted if -// and only if this function returns true. -// * When an individual TLF is shown (e.g. as a row), it should be badged if -// and only if this funciton returns true. -// -// If we add more badges, this function should be updated. -export const tlfIsBadged = (tlf: T.FS.Tlf) => !tlf.isIgnored && tlf.isNew - -export const pathsInSameTlf = (a: T.FS.Path, b: T.FS.Path): boolean => { - const elemsA = T.FS.getPathElements(a) - const elemsB = T.FS.getPathElements(b) - return elemsA.length >= 3 && elemsB.length >= 3 && elemsA[1] === elemsB[1] && elemsA[2] === elemsB[2] -} - -const slashKeybaseSlashLength = '/keybase/'.length -// TODO: move this to Go -export const escapePath = (path: T.FS.Path): string => - 'keybase://' + - encodeURIComponent(T.FS.pathToString(path).slice(slashKeybaseSlashLength)).replace( - // We need to do this because otherwise encodeURIComponent would encode - // "/"s. - /%2F/g, - '/' - ) - -export const parsedPathRoot: T.FS.ParsedPathRoot = {kind: T.FS.PathKind.Root} - -export const parsedPathPrivateList: T.FS.ParsedPathTlfList = { - kind: T.FS.PathKind.TlfList, - tlfType: T.FS.TlfType.Private, -} - -export const parsedPathPublicList: T.FS.ParsedPathTlfList = { - kind: T.FS.PathKind.TlfList, - tlfType: T.FS.TlfType.Public, -} - -export const parsedPathTeamList: T.FS.ParsedPathTlfList = { - kind: T.FS.PathKind.TlfList, - tlfType: T.FS.TlfType.Team, -} - -const splitTlfIntoReadersAndWriters = ( - tlf: string -): { - readers?: Array - writers: Array -} => { - const [w, r] = tlf.split('#') - return { - readers: r ? r.split(',').filter(i => !!i) : undefined, - writers: w?.split(',').filter(i => !!i) ?? [], - } -} - -// returns parsedPathRoot if unknown -export const parsePath = (path: T.FS.Path): T.FS.ParsedPath => { - const elems = T.FS.getPathElements(path) - if (elems.length <= 1) { - return parsedPathRoot - } - switch (elems[1]) { - case 'private': - switch (elems.length) { - case 2: - return parsedPathPrivateList - case 3: - return { - kind: T.FS.PathKind.GroupTlf, - tlfName: elems[2]!, - tlfType: T.FS.TlfType.Private, - ...splitTlfIntoReadersAndWriters(elems[2]!), - } - default: - return { - kind: T.FS.PathKind.InGroupTlf, - rest: elems.slice(3), - tlfName: elems[2] ?? '', - tlfType: T.FS.TlfType.Private, - ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), - } - } - case 'public': - switch (elems.length) { - case 2: - return parsedPathPublicList - case 3: - return { - kind: T.FS.PathKind.GroupTlf, - tlfName: elems[2]!, - tlfType: T.FS.TlfType.Public, - ...splitTlfIntoReadersAndWriters(elems[2]!), - } - default: - return { - kind: T.FS.PathKind.InGroupTlf, - rest: elems.slice(3), - tlfName: elems[2] ?? '', - tlfType: T.FS.TlfType.Public, - ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), - } - } - case 'team': - switch (elems.length) { - case 2: - return parsedPathTeamList - case 3: - return { - kind: T.FS.PathKind.TeamTlf, - team: elems[2]!, - tlfName: elems[2]!, - tlfType: T.FS.TlfType.Team, - } - default: - return { - kind: T.FS.PathKind.InTeamTlf, - rest: elems.slice(3), - team: elems[2] ?? '', - tlfName: elems[2] ?? '', - tlfType: T.FS.TlfType.Team, - } - } - default: - return parsedPathRoot - } -} - -export const rebasePathToDifferentTlf = (path: T.FS.Path, newTlfPath: T.FS.Path) => - T.FS.pathConcat(newTlfPath, T.FS.getPathElements(path).slice(3).join('/')) - -export const canChat = (path: T.FS.Path) => { - const parsedPath = parsePath(path) - switch (parsedPath.kind) { - case T.FS.PathKind.Root: - case T.FS.PathKind.TlfList: - return false - case T.FS.PathKind.GroupTlf: - case T.FS.PathKind.TeamTlf: - return true - case T.FS.PathKind.InGroupTlf: - case T.FS.PathKind.InTeamTlf: - return true - default: - return false - } -} - -export const isTeamPath = (path: T.FS.Path): boolean => { - const parsedPath = parsePath(path) - return parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team -} - -export const getChatTarget = (path: T.FS.Path, me: string): string => { - const parsedPath = parsePath(path) - if (parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team) { - return 'team conversation' - } - if (parsedPath.kind === T.FS.PathKind.GroupTlf || parsedPath.kind === T.FS.PathKind.InGroupTlf) { - if (parsedPath.writers.length === 1 && !parsedPath.readers && parsedPath.writers[0] === me) { - return 'yourself' - } - if (parsedPath.writers.length + (parsedPath.readers ? parsedPath.readers.length : 0) === 2) { - const notMe = parsedPath.writers.concat(parsedPath.readers || []).filter(u => u !== me) - if (notMe.length === 1) { - return notMe[0]! - } - } - return 'group conversation' - } - return 'conversation' -} - -export const getSharePathArrayDescription = (paths: ReadonlyArray): string => { - return !paths.length ? '' : paths.length === 1 ? T.FS.getPathName(paths[0]) : `${paths.length} items` -} - -export const getDestinationPickerPathName = (picker: T.FS.DestinationPicker): string => - picker.source.type === T.FS.DestinationPickerSource.MoveOrCopy - ? T.FS.getPathName(picker.source.path) - : picker.source.type === T.FS.DestinationPickerSource.IncomingShare - ? getSharePathArrayDescription( - picker.source.source - .map(({originalPath}) => (originalPath ? T.FS.getLocalPathName(originalPath) : '')) - .filter(Boolean) - ) - : '' - -const isPathEnabledForSync = (syncConfig: T.FS.TlfSyncConfig, path: T.FS.Path): boolean => { - switch (syncConfig.mode) { - case T.FS.TlfSyncMode.Disabled: - return false - case T.FS.TlfSyncMode.Enabled: - return true - case T.FS.TlfSyncMode.Partial: - // TODO: when we enable partial sync lookup, remember to deal with - // potential ".." traversal as well. - return syncConfig.enabledPaths.includes(path) - default: - return false - } -} - -export const getUploadIconForTlfType = ( - kbfsDaemonStatus: T.FS.KbfsDaemonStatus, - uploads: T.FS.Uploads, - tlfList: T.FS.TlfList, - tlfType: T.FS.TlfType -): T.FS.UploadIcon | undefined => { - if ( - [...tlfList].some( - ([_, tlf]) => - tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict - ) - ) { - return T.FS.UploadIcon.UploadingStuck - } - - const prefix = T.FS.pathToString(T.FS.getTlfTypePathFromTlfType(tlfType)) - if ( - [...uploads.syncingPaths].some(p => T.FS.pathToString(p).startsWith(prefix)) || - [...uploads.writingToJournal.keys()].some(p => T.FS.pathToString(p).startsWith(prefix)) - ) { - return kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline - ? T.FS.UploadIcon.AwaitingToUpload - : T.FS.UploadIcon.Uploading - } - - return undefined -} - -export const tlfIsStuckInConflict = (tlf: T.FS.Tlf) => - tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict - -export const getPathStatusIconInMergeProps = ( - kbfsDaemonStatus: T.FS.KbfsDaemonStatus, - tlf: T.Immutable, - pathItem: T.Immutable, - uploadingPaths: T.Immutable>, - path: T.Immutable -): T.FS.PathStatusIcon => { - // There's no upload or sync for local conflict view. - if (tlf.conflictState.type === T.FS.ConflictStateType.ManualResolvingLocalView) { - return T.FS.LocalConflictStatus - } - - // uploading state has higher priority - if (uploadingPaths.has(path)) { - // eslint-disable-next-line - return tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict - ? T.FS.UploadIcon.UploadingStuck - : kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline - ? T.FS.UploadIcon.AwaitingToUpload - : T.FS.UploadIcon.Uploading - } - if (!isPathEnabledForSync(tlf.syncConfig, path)) { - return T.FS.NonUploadStaticSyncStatus.OnlineOnly - } - - if (pathItem === unknownPathItem && tlf.syncConfig.mode !== T.FS.TlfSyncMode.Disabled) { - return T.FS.NonUploadStaticSyncStatus.Unknown - } - - // TODO: what about 'sync-error'? - - // We don't have an upload state, and sync is enabled for this path. - switch (pathItem.prefetchStatus.state) { - case T.FS.PrefetchState.NotStarted: - return T.FS.NonUploadStaticSyncStatus.AwaitingToSync - case T.FS.PrefetchState.Complete: - return T.FS.NonUploadStaticSyncStatus.Synced - case T.FS.PrefetchState.InProgress: { - if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { - return T.FS.NonUploadStaticSyncStatus.AwaitingToSync - } - const inProgress: T.FS.PrefetchInProgress = pathItem.prefetchStatus - if (inProgress.bytesTotal === 0) { - return T.FS.NonUploadStaticSyncStatus.AwaitingToSync - } - return inProgress.bytesFetched / inProgress.bytesTotal - } - default: - return T.FS.NonUploadStaticSyncStatus.Unknown - } -} - export const makeActionsForDestinationPickerOpen = (index: number, path: T.FS.Path) => { useFSState.getState().dispatch.setDestinationPickerParentPath(index, path) navigateAppend({props: {index}, selected: 'destinationPicker'}) } -export const fsRootRouteForNav1 = isMobile ? [Tabs.settingsTab, settingsFsTab] : [Tabs.fsTab] - -export const getMainBannerType = ( - kbfsDaemonStatus: T.FS.KbfsDaemonStatus, - overallSyncStatus: T.FS.OverallSyncStatus -): T.FS.MainBannerType => { - if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { - return T.FS.MainBannerType.Offline - } else if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Trying) { - return T.FS.MainBannerType.TryingToConnect - } else if (overallSyncStatus.diskSpaceStatus === T.FS.DiskSpaceStatus.Error) { - return T.FS.MainBannerType.OutOfSpace - } else { - return T.FS.MainBannerType.None - } -} - -export const isFolder = (path: T.FS.Path, pathItem: T.FS.PathItem) => - T.FS.getPathLevel(path) <= 3 || pathItem.type === T.FS.PathType.Folder - -export const isInTlf = (path: T.FS.Path) => T.FS.getPathLevel(path) > 2 - -export const humanizeBytes = (n: number, numDecimals: number): string => { - const kb = 1024 - const mb = kb * 1024 - const gb = mb * 1024 - - if (n < kb) { - return `${n} bytes` - } else if (n < mb) { - return `${(n / kb).toFixed(numDecimals)} KB` - } else if (n < gb) { - return `${(n / mb).toFixed(numDecimals)} MB` - } - return `${(n / gb).toFixed(numDecimals)} GB` -} - -export const humanizeBytesOfTotal = (n: number, d: number): string => { - const kb = 1024 - const mb = kb * 1024 - const gb = mb * 1024 - - if (d < kb) { - return `${n} of ${d} bytes` - } else if (d < mb) { - return `${(n / kb).toFixed(2)} of ${(d / kb).toFixed(2)} KB` - } else if (d < gb) { - return `${(n / mb).toFixed(2)} of ${(d / mb).toFixed(2)} MB` - } - return `${(n / gb).toFixed(2)} of ${(d / gb).toFixed(2)} GB` -} - -export const hasPublicTag = (path: T.FS.Path): boolean => { - const publicPrefix = '/keybase/public/' - // The slash after public in `publicPrefix` prevents /keybase/public from counting. - return T.FS.pathToString(path).startsWith(publicPrefix) -} - -export const getPathUserSetting = ( - pathUserSettings: T.Immutable>, - path: T.Immutable -): T.FS.PathUserSetting => - pathUserSettings.get(path) || - (T.FS.getPathLevel(path) < 3 ? defaultTlfListPathUserSetting : defaultPathUserSetting) - -export const showSortSetting = ( - path: T.FS.Path, - pathItem: T.FS.PathItem, - kbfsDaemonStatus: T.FS.KbfsDaemonStatus -) => - !isMobile && - path !== defaultPath && - (T.FS.getPathLevel(path) === 2 || (pathItem.type === T.FS.PathType.Folder && !!pathItem.children.size)) && - !isOfflineUnsynced(kbfsDaemonStatus, pathItem, path) - -export const getSoftError = (softErrors: T.FS.SoftErrors, path: T.FS.Path): T.FS.SoftError | undefined => { - const pathError = softErrors.pathErrors.get(path) - if (pathError) { - return pathError - } - if (!softErrors.tlfErrors.size) { - return undefined - } - const tlfPath = getTlfPath(path) - return (tlfPath && softErrors.tlfErrors.get(tlfPath)) || undefined -} - -export const hasSpecialFileElement = (path: T.FS.Path): boolean => - T.FS.getPathElements(path).some(elem => elem.startsWith('.kbfs')) - -export const sfmiInfoLoaded = (settings: T.FS.Settings, driverStatus: T.FS.DriverStatus): boolean => - settings.loaded && driverStatus !== driverStatusUnknown - -// This isn't perfect since it doesn't cover the case of multi-writer public -// TLFs or where a team TLF is readonly to the user. But to do that we'd need -// some new caching in KBFS to plumb it into the Tlfs structure without -// awful overhead. -export const hideOrDisableInDestinationPicker = ( - tlfType: T.FS.TlfType, - name: string, - username: string, - destinationPickerIndex?: number -) => typeof destinationPickerIndex === 'number' && tlfType === T.FS.TlfType.Public && name !== username - const noAccessErrorCodes: Array = [ T.RPCGen.StatusCode.scsimplefsnoaccess, T.RPCGen.StatusCode.scteamnotfound, @@ -1021,7 +276,7 @@ export const errorToActionOrThrow = (error: unknown, path?: T.FS.Path) => { return } if (path && code && noAccessErrorCodes.includes(code)) { - const tlfPath = getTlfPath(path) + const tlfPath = Constants.getTlfPath(path) if (tlfPath) { useFSState.getState().dispatch.setTlfSoftError(tlfPath, T.FS.SoftError.NoAccess) return @@ -1076,17 +331,17 @@ const initialStore: Store = { errors: [], fileContext: new Map(), folderViewFilter: undefined, - kbfsDaemonStatus: unknownKbfsDaemonStatus, + kbfsDaemonStatus: Constants.unknownKbfsDaemonStatus, lastPublicBannerClosedTlf: '', - overallSyncStatus: emptyOverallSyncStatus, + overallSyncStatus: Constants.emptyOverallSyncStatus, pathInfos: new Map(), - pathItemActionMenu: emptyPathItemActionMenu, + pathItemActionMenu: Constants.emptyPathItemActionMenu, pathItems: new Map(), pathUserSettings: new Map(), - settings: emptySettings, + settings: Constants.emptySettings, sfmi: { directMountDir: '', - driverStatus: defaultDriverStatus, + driverStatus: Constants.defaultDriverStatus, preferredMountDirs: [], }, softErrors: { @@ -1124,7 +379,7 @@ export interface State extends Store { driverDisabling: () => void driverEnable: (isRetry?: boolean) => void driverKextPermissionError: () => void - dynamic: { + defer: { afterDriverDisable?: () => void afterDriverDisabling?: () => void afterDriverEnabled?: (isRetry: boolean) => void @@ -1145,6 +400,8 @@ export interface State extends Store { refreshMountDirsDesktop?: () => void setSfmiBannerDismissedDesktop?: (dismissed: boolean) => void uploadFromDragAndDropDesktop?: (parentPath: T.FS.Path, localPaths: Array) => void + onBadgeApp?: (key: 'kbfsUploading' | 'outOfSpace', on: boolean) => void + onSetBadgeCounts?: (counts: Map) => void } editError: (editID: T.FS.EditID, error: string) => void editSuccess: (editID: T.FS.EditID) => void @@ -1200,7 +457,6 @@ export interface State extends Store { setTlfsAsUnloaded: () => void setTlfSyncConfig: (tlfPath: T.FS.Path, enabled: boolean) => void setSorting: (path: T.FS.Path, sortSetting: T.FS.SortSetting) => void - setupSubscriptions: () => Promise showIncomingShare: (initialDestinationParentPath: T.FS.Path) => void showMoveOrCopy: (initialDestinationParentPath: T.FS.Path) => void startManualConflictResolution: (tlfPath: T.FS.Path) => void @@ -1218,13 +474,21 @@ export interface State extends Store { getUploadIconForFilesTab: () => T.FS.UploadIcon | undefined } +const emptyPrefetchInProgress: T.FS.PrefetchInProgress = { + bytesFetched: 0, + bytesTotal: 0, + endEstimate: 0, + startTime: 0, + state: T.FS.PrefetchState.InProgress, +} + const getPrefetchStatusFromRPC = ( prefetchStatus: T.RPCGen.PrefetchStatus, prefetchProgress: T.RPCGen.PrefetchProgress ) => { switch (prefetchStatus) { case T.RPCGen.PrefetchStatus.notStarted: - return prefetchNotStarted + return Constants.prefetchNotStarted case T.RPCGen.PrefetchStatus.inProgress: return { ...emptyPrefetchInProgress, @@ -1234,9 +498,9 @@ const getPrefetchStatusFromRPC = ( startTime: prefetchProgress.start, } case T.RPCGen.PrefetchStatus.complete: - return prefetchComplete + return Constants.prefetchComplete default: - return prefetchNotStarted + return Constants.prefetchNotStarted } } @@ -1253,21 +517,21 @@ const makeEntry = (d: T.RPCGen.Dirent, children?: Set): T.FS.PathItem => switch (d.direntType) { case T.RPCGen.DirentType.dir: return { - ...emptyFolder, + ...Constants.emptyFolder, ...direntToMetadata(d), children: new Set(children || []), progress: children ? T.FS.ProgressType.Loaded : T.FS.ProgressType.Pending, } as T.FS.PathItem case T.RPCGen.DirentType.sym: return { - ...emptySymlink, + ...Constants.emptySymlink, ...direntToMetadata(d), // TODO: plumb link target } as T.FS.PathItem case T.RPCGen.DirentType.file: case T.RPCGen.DirentType.exec: return { - ...emptyFile, + ...Constants.emptyFile, ...direntToMetadata(d), } as T.FS.PathItem } @@ -1379,7 +643,7 @@ export const useFSState = Z.createZustand((set, get) => { try { await T.RPCGen.SimpleFSSimpleFSOpenRpcPromise( { - dest: pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), + dest: Constants.pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), flags: T.RPCGen.OpenFlags.directory, opID: makeUUID(), }, @@ -1395,10 +659,10 @@ export const useFSState = Z.createZustand((set, get) => { try { const opID = makeUUID() await T.RPCGen.SimpleFSSimpleFSMoveRpcPromise({ - dest: pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), + dest: Constants.pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), opID, overwriteExistingFiles: false, - src: pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.originalName)), + src: Constants.pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.originalName)), }) await T.RPCGen.SimpleFSSimpleFSWaitRpcPromise({opID}, S.waitingKeyFSCommitEdit) get().dispatch.editSuccess(editID) @@ -1422,13 +686,37 @@ export const useFSState = Z.createZustand((set, get) => { } ignorePromise(f()) }, + defer: { + afterDriverDisable: undefined, + afterDriverDisabling: undefined, + afterDriverEnabled: undefined, + afterKbfsDaemonRpcStatusChanged: undefined, + finishedDownloadWithIntentMobile: undefined, + finishedRegularDownloadMobile: undefined, + onBadgeApp: () => { + throw new Error('onBadgeApp not implemented') + }, + onSetBadgeCounts: () => { + throw new Error('onSetBadgeCounts not implemented') + }, + openAndUploadDesktop: undefined, + openFilesFromWidgetDesktop: undefined, + openLocalPathInSystemFileManagerDesktop: undefined, + openPathInSystemFileManagerDesktop: undefined, + openSecurityPreferencesDesktop: undefined, + pickAndUploadMobile: undefined, + refreshDriverStatusDesktop: undefined, + refreshMountDirsDesktop: undefined, + setSfmiBannerDismissedDesktop: undefined, + uploadFromDragAndDropDesktop: undefined, + }, deleteFile: path => { const f = async () => { const opID = makeUUID() try { await T.RPCGen.SimpleFSSimpleFSRemoveRpcPromise({ opID, - path: pathToRPCPath(path), + path: Constants.pathToRPCPath(path), recursive: true, }) await T.RPCGen.SimpleFSSimpleFSWaitRpcPromise({opID}) @@ -1467,7 +755,7 @@ export const useFSState = Z.createZustand((set, get) => { await requestPermissionsToWrite() const downloadID = await T.RPCGen.SimpleFSSimpleFSStartDownloadRpcPromise({ isRegularDownload: type === 'download', - path: pathToRPCPath(path).kbfs, + path: Constants.pathToRPCPath(path).kbfs, }) if (type !== 'download') { get().dispatch.setPathItemActionMenuDownload( @@ -1479,7 +767,7 @@ export const useFSState = Z.createZustand((set, get) => { ignorePromise(f()) }, driverDisable: () => { - get().dispatch.dynamic.afterDriverDisable?.() + get().dispatch.defer.afterDriverDisable?.() }, driverDisabling: () => { set(s => { @@ -1487,7 +775,7 @@ export const useFSState = Z.createZustand((set, get) => { s.sfmi.driverStatus.isDisabling = true } }) - get().dispatch.dynamic.afterDriverDisabling?.() + get().dispatch.defer.afterDriverDisabling?.() }, driverEnable: isRetry => { set(s => { @@ -1495,7 +783,7 @@ export const useFSState = Z.createZustand((set, get) => { s.sfmi.driverStatus.isEnabling = true } }) - get().dispatch.dynamic.afterDriverEnabled?.(!!isRetry) + get().dispatch.defer.afterDriverEnabled?.(!!isRetry) }, driverKextPermissionError: () => { set(s => { @@ -1505,24 +793,6 @@ export const useFSState = Z.createZustand((set, get) => { } }) }, - dynamic: { - afterDriverDisable: undefined, - afterDriverDisabling: undefined, - afterDriverEnabled: undefined, - afterKbfsDaemonRpcStatusChanged: undefined, - finishedDownloadWithIntentMobile: undefined, - finishedRegularDownloadMobile: undefined, - openAndUploadDesktop: undefined, - openFilesFromWidgetDesktop: undefined, - openLocalPathInSystemFileManagerDesktop: undefined, - openPathInSystemFileManagerDesktop: undefined, - openSecurityPreferencesDesktop: undefined, - pickAndUploadMobile: undefined, - refreshDriverStatusDesktop: undefined, - refreshMountDirsDesktop: undefined, - setSfmiBannerDismissedDesktop: undefined, - uploadFromDragAndDropDesktop: undefined, - }, editError: (editID, error) => { set(s => { const e = s.edits.get(editID) @@ -1554,7 +824,7 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs[visibility].set( elems[2] ?? '', T.castDraft({ - ...(s.tlfs[visibility].get(elems[2] ?? '') || unknownTlf), + ...(s.tlfs[visibility].get(elems[2] ?? '') || Constants.unknownTlf), isIgnored: false, }) ) @@ -1571,7 +841,7 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs[visibility].set( elems[2] ?? '', T.castDraft({ - ...(s.tlfs[visibility].get(elems[2] ?? '') || unknownTlf), + ...(s.tlfs[visibility].get(elems[2] ?? '') || Constants.unknownTlf), isIgnored: true, }) ) @@ -1581,7 +851,7 @@ export const useFSState = Z.createZustand((set, get) => { favoritesLoad: () => { const f = async () => { try { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } const results = await T.RPCGen.SimpleFSSimpleFSListFavoritesRpcPromise() @@ -1606,7 +876,7 @@ export const useFSState = Z.createZustand((set, get) => { const tlfType = rpcFolderTypeToTlfType(folder.folderType) const tlfName = tlfType === T.FS.TlfType.Private || tlfType === T.FS.TlfType.Public - ? tlfToPreferredOrder(folder.name, storeRegistry.getState('current-user').username) + ? tlfToPreferredOrder(folder.name, useCurrentUserState.getState().username) : folder.name tlfType && payload[tlfType].set( @@ -1634,8 +904,8 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs.loaded = true }) const counts = new Map() - counts.set(Tabs.fsTab, computeBadgeNumberForAll(get().tlfs)) - storeRegistry.getState('notifications').dispatch.setBadgeCounts(counts) + counts.set(Tabs.fsTab, Constants.computeBadgeNumberForAll(get().tlfs)) + get().dispatch.defer.onSetBadgeCounts?.(counts) } } catch (e) { errorToActionOrThrow(e) @@ -1647,7 +917,7 @@ export const useFSState = Z.createZustand((set, get) => { finishManualConflictResolution: localViewTlfPath => { const f = async () => { await T.RPCGen.SimpleFSSimpleFSFinishResolvingConflictRpcPromise({ - path: pathToRPCPath(localViewTlfPath), + path: Constants.pathToRPCPath(localViewTlfPath), }) get().dispatch.favoritesLoad() } @@ -1662,14 +932,14 @@ export const useFSState = Z.createZustand((set, get) => { depth: 1, filter: T.RPCGen.ListFilter.filterSystemHidden, opID, - path: pathToRPCPath(rootPath), + path: Constants.pathToRPCPath(rootPath), refreshSubscription: false, }) } else { await T.RPCGen.SimpleFSSimpleFSListRpcPromise({ filter: T.RPCGen.ListFilter.filterSystemHidden, opID, - path: pathToRPCPath(rootPath), + path: Constants.pathToRPCPath(rootPath), refreshSubscription: false, }) } @@ -1719,11 +989,11 @@ export const useFSState = Z.createZustand((set, get) => { // Get metadata fields of the directory that we just loaded from state to // avoid overriding them. - const rootPathItem = getPathItem(get().pathItems, rootPath) + const rootPathItem = Constants.getPathItem(get().pathItems, rootPath) const rootFolder: T.FS.FolderPathItem = { ...(rootPathItem.type === T.FS.PathType.Folder ? rootPathItem - : {...emptyFolder, name: T.FS.getPathName(rootPath)}), + : {...Constants.emptyFolder, name: T.FS.getPathName(rootPath)}), children: new Set(childMap.get(rootPath)), progress: T.FS.ProgressType.Loaded, } @@ -1734,7 +1004,7 @@ export const useFSState = Z.createZustand((set, get) => { ] as const) set(s => { pathItems.forEach((pathItemFromAction, path) => { - const oldPathItem = getPathItem(s.pathItems, path) + const oldPathItem = Constants.getPathItem(s.pathItems, path) const newPathItem = updatePathItem(oldPathItem, pathItemFromAction) oldPathItem.type === T.FS.PathType.Folder && oldPathItem.children.forEach( @@ -1751,7 +1021,7 @@ export const useFSState = Z.createZustand((set, get) => { if (edit.type !== T.FS.EditType.Rename) { return true } - const parent = getPathItem(s.pathItems, edit.parentPath) + const parent = Constants.getPathItem(s.pathItems, edit.parentPath) if (parent.type === T.FS.PathType.Folder && parent.children.has(edit.name)) { return true } @@ -1878,7 +1148,7 @@ export const useFSState = Z.createZustand((set, get) => { } subscribeAndLoadJournalStatus() // how this works isn't great. This function gets called way early before we set this - get().dispatch.dynamic.afterKbfsDaemonRpcStatusChanged?.() + get().dispatch.defer.afterKbfsDaemonRpcStatusChanged?.() }, letResetUserBackIn: (id, username) => { const f = async () => { @@ -1898,12 +1168,12 @@ export const useFSState = Z.createZustand((set, get) => { } try { const {folder, isFavorite, isIgnored, isNew} = await T.RPCGen.SimpleFSSimpleFSGetFolderRpcPromise({ - path: pathToRPCPath(tlfPath).kbfs, + path: Constants.pathToRPCPath(tlfPath).kbfs, }) const tlfType = rpcFolderTypeToTlfType(folder.folderType) const tlfName = tlfType === T.FS.TlfType.Private || tlfType === T.FS.TlfType.Public - ? tlfToPreferredOrder(folder.name, storeRegistry.getState('current-user').username) + ? tlfToPreferredOrder(folder.name, useCurrentUserState.getState().username) : folder.name if (tlfType) { @@ -2005,7 +1275,7 @@ export const useFSState = Z.createZustand((set, get) => { const f = async () => { try { const res = await T.RPCGen.SimpleFSSimpleFSGetGUIFileContextRpcPromise({ - path: pathToRPCPath(path).kbfs, + path: Constants.pathToRPCPath(path).kbfs, }) set(s => { @@ -2058,7 +1328,7 @@ export const useFSState = Z.createZustand((set, get) => { try { const dirent = await T.RPCGen.SimpleFSSimpleFSStatRpcPromise( { - path: pathToRPCPath(path), + path: Constants.pathToRPCPath(path), refreshSubscription: false, }, S.waitingKeyFSStat @@ -2066,7 +1336,7 @@ export const useFSState = Z.createZustand((set, get) => { const pathItem = makeEntry(dirent) set(s => { - const oldPathItem = getPathItem(s.pathItems, path) + const oldPathItem = Constants.getPathItem(s.pathItems, path) s.pathItems.set(path, T.castDraft(updatePathItem(oldPathItem, pathItem))) s.softErrors.pathErrors.delete(path) s.softErrors.tlfErrors.delete(path) @@ -2103,13 +1373,13 @@ export const useFSState = Z.createZustand((set, get) => { }, loadTlfSyncConfig: tlfPath => { const f = async () => { - const parsedPath = parsePath(tlfPath) + const parsedPath = Constants.parsePath(tlfPath) if (parsedPath.kind !== T.FS.PathKind.GroupTlf && parsedPath.kind !== T.FS.PathKind.TeamTlf) { return } try { const result = await T.RPCGen.SimpleFSSimpleFSFolderSyncConfigAndStatusRpcPromise({ - path: pathToRPCPath(tlfPath), + path: Constants.pathToRPCPath(tlfPath), }) const syncConfig = getSyncConfigFromRPC(parsedPath.tlfName, parsedPath.tlfType, result.config) const tlfName = parsedPath.tlfName @@ -2117,17 +1387,17 @@ export const useFSState = Z.createZustand((set, get) => { set(s => { const oldTlfList = s.tlfs[tlfType] - const oldTlfFromFavorites = oldTlfList.get(tlfName) || unknownTlf - if (oldTlfFromFavorites !== unknownTlf) { + const oldTlfFromFavorites = oldTlfList.get(tlfName) || Constants.unknownTlf + if (oldTlfFromFavorites !== Constants.unknownTlf) { s.tlfs[tlfType] = T.castDraft( new Map([...oldTlfList, [tlfName, {...oldTlfFromFavorites, syncConfig}]]) ) return } - const tlfPath = T.FS.pathConcat(T.FS.pathConcat(defaultPath, tlfType), tlfName) - const oldTlfFromAdditional = s.tlfs.additionalTlfs.get(tlfPath) || unknownTlf - if (oldTlfFromAdditional !== unknownTlf) { + const tlfPath = T.FS.pathConcat(T.FS.pathConcat(Constants.defaultPath, tlfType), tlfName) + const oldTlfFromAdditional = s.tlfs.additionalTlfs.get(tlfPath) || Constants.unknownTlf + if (oldTlfFromAdditional !== Constants.unknownTlf) { s.tlfs.additionalTlfs = T.castDraft( new Map([...s.tlfs.additionalTlfs, [tlfPath, {...oldTlfFromAdditional, syncConfig}]]) ) @@ -2189,7 +1459,7 @@ export const useFSState = Z.createZustand((set, get) => { zState.destinationPicker.source.type === T.FS.DestinationPickerSource.MoveOrCopy ? [ { - dest: pathToRPCPath( + dest: Constants.pathToRPCPath( T.FS.pathConcat( destinationParentPath, T.FS.getPathName(zState.destinationPicker.source.path) @@ -2197,14 +1467,14 @@ export const useFSState = Z.createZustand((set, get) => { ), opID: makeUUID(), overwriteExistingFiles: false, - src: pathToRPCPath(zState.destinationPicker.source.path), + src: Constants.pathToRPCPath(zState.destinationPicker.source.path), }, ] : zState.destinationPicker.source.source .map(item => ({originalPath: item.originalPath ?? '', scaledPath: item.scaledPath})) .filter(({originalPath}) => !!originalPath) .map(({originalPath, scaledPath}) => ({ - dest: pathToRPCPath( + dest: Constants.pathToRPCPath( T.FS.pathConcat( destinationParentPath, T.FS.getLocalPathName(originalPath) @@ -2216,7 +1486,7 @@ export const useFSState = Z.createZustand((set, get) => { src: { PathType: T.RPCGen.PathType.local, local: T.FS.getNormalizedLocalPath( - storeRegistry.getState('config').incomingShareUseOriginal + useConfigState.getState().incomingShareUseOriginal ? originalPath : scaledPath || originalPath ), @@ -2241,7 +1511,7 @@ export const useFSState = Z.createZustand((set, get) => { ignorePromise(f()) }, newFolderRow: parentPath => { - const parentPathItem = getPathItem(get().pathItems, parentPath) + const parentPathItem = Constants.getPathItem(get().pathItems, parentPath) if (parentPathItem.type !== T.FS.PathType.Folder) { console.warn(`bad parentPath: ${parentPathItem.type}`) return @@ -2258,7 +1528,7 @@ export const useFSState = Z.createZustand((set, get) => { set(s => { s.edits.set(makeEditID(), { - ...emptyNewFolder, + ...Constants.emptyNewFolder, name: newFolderName, originalName: newFolderName, parentPath, @@ -2375,12 +1645,12 @@ export const useFSState = Z.createZustand((set, get) => { if (totalSyncingBytes <= 0 && !syncingPaths?.length) { break } - storeRegistry.getState('notifications').dispatch.badgeApp('kbfsUploading', true) + get().dispatch.defer.onBadgeApp?.('kbfsUploading', true) await timeoutPromise(getWaitDuration(endEstimate || undefined, 100, 4000)) // 0.1s to 4s } } finally { pollJournalStatusPolling = false - storeRegistry.getState('notifications').dispatch.badgeApp('kbfsUploading', false) + get().dispatch.defer.onBadgeApp?.('kbfsUploading', false) get().dispatch.checkKbfsDaemonRpcStatus() } } @@ -2423,7 +1693,7 @@ export const useFSState = Z.createZustand((set, get) => { set(s => { s.sfmi.driverStatus = driverStatus }) - get().dispatch.dynamic.refreshMountDirsDesktop?.() + get().dispatch.defer.refreshMountDirsDesktop?.() }, setEditName: (editID, name) => { set(s => { @@ -2488,7 +1758,7 @@ export const useFSState = Z.createZustand((set, get) => { if (old) { old.sort = sortSetting } else { - s.pathUserSettings.set(path, {...defaultPathUserSetting, sort: sortSetting}) + s.pathUserSettings.set(path, {...Constants.defaultPathUserSetting, sort: sortSetting}) } }) }, @@ -2515,7 +1785,7 @@ export const useFSState = Z.createZustand((set, get) => { await T.RPCGen.SimpleFSSimpleFSSetFolderSyncConfigRpcPromise( { config: {mode: enabled ? T.RPCGen.FolderSyncMode.enabled : T.RPCGen.FolderSyncMode.disabled}, - path: pathToRPCPath(tlfPath), + path: Constants.pathToRPCPath(tlfPath), }, S.waitingKeyFSSyncToggle ) @@ -2528,10 +1798,6 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs.loaded = false }) }, - setupSubscriptions: async () => { - const initPlatformSpecific = await import('./platform-specific') - initPlatformSpecific.default() - }, showIncomingShare: initialDestinationParentPath => { set(s => { if (s.destinationPicker.source.type !== T.FS.DestinationPickerSource.IncomingShare) { @@ -2539,9 +1805,7 @@ export const useFSState = Z.createZustand((set, get) => { } s.destinationPicker.destinationParentPath = [initialDestinationParentPath] }) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) + navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) }, showMoveOrCopy: initialDestinationParentPath => { set(s => { @@ -2549,21 +1813,19 @@ export const useFSState = Z.createZustand((set, get) => { s.destinationPicker.source.type === T.FS.DestinationPickerSource.MoveOrCopy ? s.destinationPicker.source : { - path: defaultPath, + path: Constants.defaultPath, type: T.FS.DestinationPickerSource.MoveOrCopy, } s.destinationPicker.destinationParentPath = [initialDestinationParentPath] }) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) + navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) }, startManualConflictResolution: tlfPath => { const f = async () => { await T.RPCGen.SimpleFSSimpleFSClearConflictStateRpcPromise({ - path: pathToRPCPath(tlfPath), + path: Constants.pathToRPCPath(tlfPath), }) get().dispatch.favoritesLoad() } @@ -2641,12 +1903,12 @@ export const useFSState = Z.createZustand((set, get) => { body: 'You are out of disk space. Some folders could not be synced.', sound: true, }) - storeRegistry.getState('notifications').dispatch.badgeApp('outOfSpace', status.outOfSyncSpace) + get().dispatch.defer.onBadgeApp?.('outOfSpace', status.outOfSyncSpace) break } case T.FS.DiskSpaceStatus.Warning: { - const threshold = humanizeBytes(get().settings.spaceAvailableNotificationThreshold, 0) + const threshold = Constants.humanizeBytes(get().settings.spaceAvailableNotificationThreshold, 0) NotifyPopup('Disk Space Low', { body: `You have less than ${threshold} of storage space left.`, }) @@ -2682,7 +1944,7 @@ export const useFSState = Z.createZustand((set, get) => { try { await T.RPCGen.SimpleFSSimpleFSStartUploadRpcPromise({ sourceLocalPath: T.FS.getNormalizedLocalPath(localPath), - targetParentPath: pathToRPCPath(parentPath).kbfs, + targetParentPath: Constants.pathToRPCPath(parentPath).kbfs, }) } catch (err) { errorToActionOrThrow(err) diff --git a/shared/constants/git/index.tsx b/shared/stores/git.tsx similarity index 91% rename from shared/constants/git/index.tsx rename to shared/stores/git.tsx index ffe070ae3f3f..6e7854c3ab9c 100644 --- a/shared/constants/git/index.tsx +++ b/shared/stores/git.tsx @@ -1,11 +1,18 @@ -import * as S from '../strings' -import * as T from '../types' -import {ignorePromise} from '../utils' +import * as S from '@/constants/strings' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' import * as EngineGen from '@/actions/engine-gen-gen' import * as dateFns from 'date-fns' import * as Z from '@/util/zustand' import debounce from 'lodash/debounce' -import {storeRegistry} from '../store-registry' +import {navigateAppend} from '@/constants/router2' +import {useConfigState} from '@/stores/config' + +type Store = T.Immutable<{ + readonly error?: Error + readonly idToInfo: Map + readonly isNew?: Set +}> const parseRepos = (results: ReadonlyArray) => { const errors: Array = [] @@ -56,13 +63,13 @@ const parseRepoError = (result: T.RPCGen.GitRepoResult): Error => { return new Error(`Git repo error: ${errStr}`) } -const initialStore: T.Git.State = { +const initialStore: Store = { error: undefined, idToInfo: new Map(), isNew: new Set(), } -export interface State extends T.Git.State { +export interface State extends Store { dispatch: { setError: (err?: Error) => void clearBadges: () => void @@ -105,7 +112,7 @@ export const useGitState = Z.createZustand((set, get) => { async () => { const results = await T.RPCGen.gitGetAllGitMetadataRpcPromise(undefined, S.waitingKeyGitLoading) const {errors, repos} = parseRepos(results || []) - const {setGlobalError} = storeRegistry.getState('config').dispatch + const {setGlobalError} = useConfigState.getState().dispatch errors.forEach(e => setGlobalError(e)) set(s => { s.idToInfo = repos @@ -151,9 +158,7 @@ export const useGitState = Z.createZustand((set, get) => { await _load() for (const [, info] of get().idToInfo) { if (info.repoID === repoID && info.teamname === teamname) { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {expanded: info.id}, selected: 'gitRoot'}) + navigateAppend({props: {expanded: info.id}, selected: 'gitRoot'}) break } } diff --git a/shared/constants/logout/index.tsx b/shared/stores/logout.tsx similarity index 88% rename from shared/constants/logout/index.tsx rename to shared/stores/logout.tsx index c1e85a767cad..1348cca9b47b 100644 --- a/shared/constants/logout/index.tsx +++ b/shared/stores/logout.tsx @@ -1,13 +1,14 @@ import logger from '@/logger' -import {ignorePromise, timeoutPromise} from '../utils' +import {ignorePromise, timeoutPromise} from '@/constants/utils' import * as T from '@/constants/types' // normally util.container but it re-exports from us so break the cycle import * as Z from '@/util/zustand' -import {settingsPasswordTab} from '../settings' -import {navigateAppend} from '../router2/util' -import {isMobile} from '../platform' -import * as Tabs from '../tabs' +import {settingsPasswordTab} from '@/constants/settings' +import {navigateAppend} from '@/constants/router2' +import {isMobile} from '@/constants/platform' +import * as Tabs from '@/constants/tabs' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ waiters: Map // if we ever restart handshake up this so we can ignore any waiters for old things diff --git a/shared/constants/notifications/index.tsx b/shared/stores/notifications.tsx similarity index 89% rename from shared/constants/notifications/index.tsx rename to shared/stores/notifications.tsx index 6d9881b80fa1..ba83285d4302 100644 --- a/shared/constants/notifications/index.tsx +++ b/shared/stores/notifications.tsx @@ -1,10 +1,11 @@ import * as Z from '@/util/zustand' import * as EngineGen from '@/actions/engine-gen-gen' -import type * as T from '../types' -import {isMobile} from '../platform' +import type * as T from '@/constants/types' +import {isMobile} from '@/constants/platform' import isEqual from 'lodash/isEqual' -import * as Tabs from '../tabs' -import {storeRegistry} from '../store-registry' +import * as Tabs from '@/constants/tabs' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' export type BadgeType = 'regular' | 'update' | 'error' | 'uploading' export type NotificationKeys = 'kbfsUploading' | 'outOfSpace' @@ -28,6 +29,9 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onFavoritesLoad?: () => void + } onEngineIncomingImpl: (action: EngineGen.Actions) => void resetState: 'default' badgeApp: (key: NotificationKeys, on: boolean) => void @@ -67,7 +71,7 @@ const badgeStateToBadgeCounts = (bs: T.RPCGen.BadgeState) => { revokedDevices.forEach(d => allDeviceChanges.add(d)) // don't see badges related to this device - const deviceID = storeRegistry.getState('current-user').deviceID + const deviceID = useCurrentUserState.getState().deviceID counts.set(Tabs.devicesTab, allDeviceChanges.size - (allDeviceChanges.has(deviceID) ? 1 : 0)) counts.set(Tabs.chatTab, bs.smallTeamBadgeCount + bs.bigTeamBadgeCount) counts.set(Tabs.gitTab, newGitRepoGlobalUniqueIDs.length) @@ -99,19 +103,24 @@ export const useNotifState = Z.createZustand((set, get) => { updateWidgetBadge(s) }) }, + defer: { + onFavoritesLoad: () => { + throw new Error('onFavoritesLoad not implemented') + }, + }, onEngineIncomingImpl: action => { switch (action.type) { case EngineGen.keybase1NotifyAuditRootAuditError: - storeRegistry - .getState('config') + useConfigState + .getState() .dispatch.setGlobalError( new Error(`Keybase is buggy, please report this: ${action.payload.params.message}`) ) break case EngineGen.keybase1NotifyAuditBoxAuditError: - storeRegistry - .getState('config') + useConfigState + .getState() .dispatch.setGlobalError( new Error( `Keybase had a problem loading a team, please report this with \`keybase log send\`: ${action.payload.params.message}` @@ -120,11 +129,11 @@ export const useNotifState = Z.createZustand((set, get) => { break case EngineGen.keybase1NotifyBadgesBadgeState: { const badgeState = action.payload.params.badgeState - storeRegistry.getState('config').dispatch.setBadgeState(badgeState) + useConfigState.getState().dispatch.setBadgeState(badgeState) const counts = badgeStateToBadgeCounts(badgeState) if (!isMobile && shouldTriggerTlfLoad(badgeState)) { - storeRegistry.getState('fs').dispatch.favoritesLoad() + get().dispatch.defer.onFavoritesLoad?.() } if (counts) { get().dispatch.setBadgeCounts(counts) diff --git a/shared/constants/people/index.tsx b/shared/stores/people.tsx similarity index 97% rename from shared/constants/people/index.tsx rename to shared/stores/people.tsx index ba8f0024f463..27a6a2746e2d 100644 --- a/shared/constants/people/index.tsx +++ b/shared/stores/people.tsx @@ -1,16 +1,17 @@ import * as EngineGen from '@/actions/engine-gen-gen' -import {ignorePromise} from '../utils' +import {ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' import invert from 'lodash/invert' import isEqual from 'lodash/isEqual' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import type {IconType} from '@/common-adapters/icon.constants-gen' // do NOT pull in all of common-adapters -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' import type {e164ToDisplay as e164ToDisplayType} from '@/util/phone-numbers' import debounce from 'lodash/debounce' -import {storeRegistry} from '../store-registry' -import {RPCError, isNetworkErr} from '../utils' +import {useConfigState} from '@/stores/config' +import {useFollowerState} from '@/stores/followers' +import {RPCError, isNetworkErr} from '@/constants/utils' // set this to true to have all todo items + a contact joined notification show up all the time const debugTodo = false as boolean @@ -387,7 +388,7 @@ export const usePeopleState = Z.createZustand((set, get) => { logger.info( 'getPeopleData: appFocused:', 'loggedIn', - storeRegistry.getState('config').loggedIn, + useConfigState.getState().loggedIn, 'action', {markViewed, numFollowSuggestionsWanted} ) @@ -397,7 +398,7 @@ export const usePeopleState = Z.createZustand((set, get) => { {markViewed, numFollowSuggestionsWanted}, getPeopleDataWaitingKey ) - const {following, followers} = storeRegistry.getState('followers') + const {following, followers} = useFollowerState.getState() const oldItems: Array = (data.items ?? []) .filter(item => !item.badged && item.data.t !== T.RPCGen.HomeScreenItemType.todo) .reduce(reduceRPCItemToPeopleItem, []) diff --git a/shared/constants/pinentry/index.tsx b/shared/stores/pinentry.tsx similarity index 92% rename from shared/constants/pinentry/index.tsx rename to shared/stores/pinentry.tsx index 1dae7f694344..8dc26ad61ace 100644 --- a/shared/constants/pinentry/index.tsx +++ b/shared/stores/pinentry.tsx @@ -1,10 +1,11 @@ import * as Z from '@/util/zustand' import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' +import * as T from '@/constants/types' import logger from '@/logger' -import {invalidPasswordErrorString} from '@/constants/config/util' -import {wrapErrors} from '../utils' +import {invalidPasswordErrorString} from '@/constants/config' +import {wrapErrors} from '@/constants/utils' +// This store has no dependencies on other stores and is safe to import directly from other stores. export type Store = T.Immutable<{ cancelLabel?: string prompt: string diff --git a/shared/constants/profile/index.tsx b/shared/stores/profile.tsx similarity index 89% rename from shared/constants/profile/index.tsx rename to shared/stores/profile.tsx index 0d01f9582e9c..154178603e1c 100644 --- a/shared/constants/profile/index.tsx +++ b/shared/stores/profile.tsx @@ -1,15 +1,16 @@ -import * as T from '../types' -import {generateGUIID, ignorePromise, wrapErrors} from '../utils' -import * as S from '../strings' +import * as T from '@/constants/types' +import {generateGUIID, ignorePromise, wrapErrors} from '@/constants/utils' +import * as S from '@/constants/strings' import * as Validators from '@/util/simple-validators' import * as Z from '@/util/zustand' import logger from '@/logger' import openURL from '@/util/open-url' import {RPCError} from '@/util/errors' import {fixCrop} from '@/util/crop' -import {clearModals, navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {navToProfile} from '../router2' +import {clearModals, navigateAppend, navigateUp} from '@/constants/router2' +import {useCurrentUserState} from '@/stores/current-user' +import {navToProfile} from '@/constants/router2' +import type {useTrackerState} from '@/stores/tracker2' type ProveGenericParams = { logoBlack: T.Tracker.SiteIconSet @@ -97,6 +98,14 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onTracker2GetDetails?: (username: string) => T.Tracker.Details | undefined + onTracker2Load?: ( + params: Parameters['dispatch']['load']>[0] + ) => void + onTracker2ShowUser?: (username: string, asTracker: boolean, skipNav?: boolean) => void + onTracker2UpdateResult?: (guiID: string, result: T.Tracker.DetailsState, reason?: string) => void + } dynamic: { afterCheckProof?: () => void cancelAddProof?: () => void @@ -265,8 +274,8 @@ export const useProfileState = Z.createZustand((set, get) => { let canceled = false const loadAfter = () => - storeRegistry.getState('tracker2').dispatch.load({ - assertion: storeRegistry.getState('current-user').username, + get().dispatch.defer.onTracker2Load?.({ + assertion: useCurrentUserState.getState().username, guiID: generateGUIID(), inTracker: false, reason: '', @@ -407,12 +416,13 @@ export const useProfileState = Z.createZustand((set, get) => { s.errorCode = error.code }) if (error.code === T.RPCGen.StatusCode.scgeneric && reason === 'appLink') { - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - "We couldn't find a valid service for proofs in this link. The link might be bad, or your Keybase app might be out of date and need to be updated." - ) - navigateAppend('keybaseLinkError') + navigateAppend({ + props: { + error: + "We couldn't find a valid service for proofs in this link. The link might be bad, or your Keybase app might be out of date and need to be updated.", + }, + selected: 'keybaseLinkError', + }) } } if (genericService) { @@ -435,7 +445,7 @@ export const useProfileState = Z.createZustand((set, get) => { backToProfile: () => { clearModals() setTimeout(() => { - get().dispatch.showUserProfile(storeRegistry.getState('current-user').username) + get().dispatch.showUserProfile(useCurrentUserState.getState().username) }, 100) }, checkProof: () => { @@ -483,6 +493,20 @@ export const useProfileState = Z.createZustand((set, get) => { clearErrors(s) }) }, + defer: { + onTracker2GetDetails: () => { + throw new Error('onTracker2GetDetails not implemented') + }, + onTracker2Load: () => { + throw new Error('onTracker2Load not implemented') + }, + onTracker2ShowUser: () => { + throw new Error('onTracker2ShowUser not implemented') + }, + onTracker2UpdateResult: () => { + throw new Error('onTracker2UpdateResult not implemented') + }, + }, dynamic: { cancelAddProof: _cancelAddProof, cancelPgpGen: undefined, @@ -494,15 +518,15 @@ export const useProfileState = Z.createZustand((set, get) => { editProfile: (bio, fullName, location) => { const f = async () => { await T.RPCGen.userProfileEditRpcPromise({bio, fullName, location}, S.waitingKeyTracker) - get().dispatch.showUserProfile(storeRegistry.getState('current-user').username) + get().dispatch.showUserProfile(useCurrentUserState.getState().username) } ignorePromise(f()) }, finishRevoking: () => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username get().dispatch.showUserProfile(username) - storeRegistry.getState('tracker2').dispatch.load({ - assertion: storeRegistry.getState('current-user').username, + get().dispatch.defer.onTracker2Load?.({ + assertion: useCurrentUserState.getState().username, guiID: generateGUIID(), inTracker: false, reason: '', @@ -597,9 +621,7 @@ export const useProfileState = Z.createZustand((set, get) => { }) const f = async () => { await T.RPCGen.proveCheckProofRpcPromise({sigID}, S.waitingKeyProfile) - storeRegistry - .getState('tracker2') - .dispatch.showUser(storeRegistry.getState('current-user').username, false) + get().dispatch.defer.onTracker2ShowUser?.(useCurrentUserState.getState().username, false) } ignorePromise(f()) }, @@ -626,7 +648,7 @@ export const useProfileState = Z.createZustand((set, get) => { set(s => { s.blockUserModal = undefined }) - storeRegistry.getState('tracker2').dispatch.load({ + get().dispatch.defer.onTracker2Load?.({ assertion: username, guiID: generateGUIID(), inTracker: false, @@ -647,10 +669,8 @@ export const useProfileState = Z.createZustand((set, get) => { }, submitRevokeProof: proofId => { const f = async () => { - const you = storeRegistry - .getState('tracker2') - .getDetails(storeRegistry.getState('current-user').username) - if (!you.assertions) return + const you = get().dispatch.defer.onTracker2GetDetails?.(useCurrentUserState.getState().username) + if (!you?.assertions) return const proof = [...you.assertions.values()].find(a => a.sigID === proofId) if (!proof) return @@ -681,7 +701,7 @@ export const useProfileState = Z.createZustand((set, get) => { const f = async () => { try { await T.RPCGen.userUnblockUserRpcPromise({username}) - storeRegistry.getState('tracker2').dispatch.load({ + get().dispatch.defer.onTracker2Load?.({ assertion: username, guiID: generateGUIID(), inTracker: false, @@ -693,9 +713,11 @@ export const useProfileState = Z.createZustand((set, get) => { } const error = _error logger.warn(`Error unblocking user ${username}`, error) - storeRegistry - .getState('tracker2') - .dispatch.updateResult(guiID, 'error', `Failed to unblock ${username}: ${error.desc}`) + get().dispatch.defer.onTracker2UpdateResult?.( + guiID, + 'error', + `Failed to unblock ${username}: ${error.desc}` + ) } } ignorePromise(f()) diff --git a/shared/constants/provision/index.tsx b/shared/stores/provision.tsx similarity index 86% rename from shared/constants/provision/index.tsx rename to shared/stores/provision.tsx index c49fcbf3c1d7..cf3f5c95f5ad 100644 --- a/shared/constants/provision/index.tsx +++ b/shared/stores/provision.tsx @@ -1,15 +1,15 @@ -import * as C from '..' -import * as T from '../types' -import {ignorePromise} from '../utils' +import * as T from '@/constants/types' +import {ignorePromise, wrapErrors} from '@/constants/utils' +import {waitingKeyProvision, waitingKeyProvisionForgotUsername} from '@/constants/strings' import * as Z from '@/util/zustand' import {RPCError} from '@/util/errors' -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' import {type CommonResponseHandler} from '@/engine/types' import isEqual from 'lodash/isEqual' -import {rpcDeviceToDevice} from '../rpc-utils' -import {invalidPasswordErrorString} from '@/constants/config/util' -import {clearModals, navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {rpcDeviceToDevice} from '@/constants/rpc-utils' +import {invalidPasswordErrorString} from '@/constants/config' +import {clearModals, navigateAppend} from '@/constants/router2' +import {useWaitingState} from '@/stores/waiting' export type Device = { deviceNumberOfType: number @@ -82,6 +82,7 @@ type Store = T.Immutable<{ inlineError?: RPCError passphrase: string provisionStep: number + startProvisionTrigger: number username: string }> const initialStore: Store = { @@ -99,6 +100,7 @@ const initialStore: Store = { inlineError: undefined, passphrase: '', provisionStep: 0, + startProvisionTrigger: 0, username: '', } @@ -121,15 +123,15 @@ export interface State extends Store { } export const useProvisionState = Z.createZustand((set, get) => { - const _cancel = C.wrapErrors((ignoreWarning?: boolean) => { - storeRegistry.getState('waiting').dispatch.clear(C.waitingKeyProvision) + const _cancel = wrapErrors((ignoreWarning?: boolean) => { + useWaitingState.getState().dispatch.clear(waitingKeyProvision) if (!ignoreWarning) { console.log('Provision: cancel called while not overloaded') } }) // add a new value to submit and clear things behind - const _updateAutoSubmit = C.wrapErrors((step: Store['autoSubmit'][0]) => { + const _updateAutoSubmit = wrapErrors((step: Store['autoSubmit'][0]) => { set(s => { const idx = s.autoSubmit.findIndex(a => a.type === step.type) if (idx !== -1) { @@ -139,16 +141,7 @@ export const useProvisionState = Z.createZustand((set, get) => { }) }) - const _setUsername = C.wrapErrors((username: string, restart: boolean = true) => { - set(s => { - s.username = username - s.autoSubmit = [{type: 'username'}] - }) - if (restart) { - get().dispatch.restartProvisioning() - } - }) - const _setPassphrase = C.wrapErrors((passphrase: string, restart: boolean = true) => { + const _setPassphrase = wrapErrors((passphrase: string, restart: boolean = true) => { set(s => { s.passphrase = passphrase }) @@ -158,7 +151,7 @@ export const useProvisionState = Z.createZustand((set, get) => { } }) - const _setDeviceName = C.wrapErrors((name: string, restart: boolean = true) => { + const _setDeviceName = wrapErrors((name: string, restart: boolean = true) => { set(s => { s.deviceName = name }) @@ -168,7 +161,7 @@ export const useProvisionState = Z.createZustand((set, get) => { } }) - const _submitDeviceSelect = C.wrapErrors((name: string, restart: boolean = true) => { + const _submitDeviceSelect = wrapErrors((name: string, restart: boolean = true) => { const devices = get().devices const selectedDevice = devices.find(d => d.name === name) if (!selectedDevice) { @@ -183,7 +176,7 @@ export const useProvisionState = Z.createZustand((set, get) => { } }) - const _submitTextCode = C.wrapErrors((_code: string) => { + const _submitTextCode = wrapErrors((_code: string) => { console.log('Provision, unwatched submitTextCode called') get().dispatch.restartProvisioning() }) @@ -204,7 +197,7 @@ export const useProvisionState = Z.createZustand((set, get) => { let cancelled = false const setupCancel = (response: CommonResponseHandler) => { set(s => { - s.dispatch.dynamic.cancel = C.wrapErrors(() => { + s.dispatch.dynamic.cancel = wrapErrors(() => { set(s => { s.dispatch.dynamic.cancel = _cancel }) @@ -231,7 +224,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = previousErr s.codePageIncomingTextCode = phrase - s.dispatch.dynamic.submitTextCode = C.wrapErrors((code: string) => { + s.dispatch.dynamic.submitTextCode = wrapErrors((code: string) => { set(s => { s.dispatch.dynamic.submitTextCode = _submitTextCode }) @@ -259,13 +252,13 @@ export const useProvisionState = Z.createZustand((set, get) => { }, incomingCallMap: { 'keybase.1.provisionUi.DisplaySecretExchanged': () => { - storeRegistry.getState('waiting').dispatch.increment(C.waitingKeyProvision) + useWaitingState.getState().dispatch.increment(waitingKeyProvision) }, 'keybase.1.provisionUi.ProvisioneeSuccess': () => {}, 'keybase.1.provisionUi.ProvisionerSuccess': () => {}, }, params: undefined, - waitingKey: C.waitingKeyProvision, + waitingKey: waitingKeyProvision, }) } catch { } finally { @@ -273,7 +266,6 @@ export const useProvisionState = Z.createZustand((set, get) => { s.dispatch.dynamic.cancel = _cancel s.dispatch.dynamic.setDeviceName = _setDeviceName s.dispatch.dynamic.setPassphrase = _setPassphrase - s.dispatch.dynamic.setUsername = _setUsername s.dispatch.dynamic.submitDeviceSelect = _submitDeviceSelect s.dispatch.dynamic.submitTextCode = _submitTextCode }) @@ -286,7 +278,15 @@ export const useProvisionState = Z.createZustand((set, get) => { cancel: _cancel, setDeviceName: _setDeviceName, setPassphrase: _setPassphrase, - setUsername: _setUsername, + setUsername: wrapErrors((username: string, restart: boolean = true) => { + set(s => { + s.username = username + s.autoSubmit = [{type: 'username'}] + }) + if (restart) { + get().dispatch.restartProvisioning() + } + }), submitDeviceSelect: _submitDeviceSelect, submitTextCode: _submitTextCode, }, @@ -296,7 +296,7 @@ export const useProvisionState = Z.createZustand((set, get) => { try { await T.RPCGen.accountRecoverUsernameWithEmailRpcPromise( {email}, - C.waitingKeyProvisionForgotUsername + waitingKeyProvisionForgotUsername ) set(s => { s.forgotUsernameResult = 'success' @@ -314,7 +314,7 @@ export const useProvisionState = Z.createZustand((set, get) => { try { await T.RPCGen.accountRecoverUsernameWithPhoneRpcPromise( {phone}, - C.waitingKeyProvisionForgotUsername + waitingKeyProvisionForgotUsername ) set(s => { s.forgotUsernameResult = 'success' @@ -366,7 +366,7 @@ export const useProvisionState = Z.createZustand((set, get) => { // Make cancel set the flag and cancel the current rpc const setupCancel = (response: CommonResponseHandler) => { set(s => { - s.dispatch.dynamic.cancel = C.wrapErrors(() => { + s.dispatch.dynamic.cancel = wrapErrors(() => { set(s => { s.dispatch.dynamic.cancel = _cancel }) @@ -397,7 +397,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = previousErr s.codePageIncomingTextCode = phrase - s.dispatch.dynamic.submitTextCode = C.wrapErrors((code: string) => { + s.dispatch.dynamic.submitTextCode = wrapErrors((code: string) => { set(s => { s.dispatch.dynamic.submitTextCode = _submitTextCode }) @@ -418,7 +418,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = errorMessage s.existingDevices = T.castDraft(existingDevices ?? []) - s.dispatch.dynamic.setDeviceName = C.wrapErrors((name: string) => { + s.dispatch.dynamic.setDeviceName = wrapErrors((name: string) => { set(s => { s.dispatch.dynamic.setDeviceName = _setDeviceName }) @@ -443,7 +443,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = '' s.devices = devices - s.dispatch.dynamic.submitDeviceSelect = C.wrapErrors((device: string) => { + s.dispatch.dynamic.submitDeviceSelect = wrapErrors((device: string) => { set(s => { s.dispatch.dynamic.submitDeviceSelect = _submitDeviceSelect }) @@ -472,7 +472,7 @@ export const useProvisionState = Z.createZustand((set, get) => { // Service asking us again due to an error? set(s => { s.error = retryLabel === invalidPasswordErrorString ? 'Incorrect password.' : retryLabel - s.dispatch.dynamic.setPassphrase = C.wrapErrors((passphrase: string) => { + s.dispatch.dynamic.setPassphrase = wrapErrors((passphrase: string) => { set(s => { s.dispatch.dynamic.setPassphrase = _setPassphrase }) @@ -502,7 +502,7 @@ export const useProvisionState = Z.createZustand((set, get) => { incomingCallMap: { 'keybase.1.loginUi.displayPrimaryPaperKey': () => {}, 'keybase.1.provisionUi.DisplaySecretExchanged': () => { - storeRegistry.getState('waiting').dispatch.increment(C.waitingKeyProvision) + useWaitingState.getState().dispatch.increment(waitingKeyProvision) }, 'keybase.1.provisionUi.ProvisioneeSuccess': () => {}, 'keybase.1.provisionUi.ProvisionerSuccess': () => {}, @@ -515,7 +515,7 @@ export const useProvisionState = Z.createZustand((set, get) => { paperKey: '', username, }, - waitingKey: C.waitingKeyProvision, + waitingKey: waitingKeyProvision, }) get().dispatch.resetState() } catch (_finalError) { @@ -551,23 +551,11 @@ export const useProvisionState = Z.createZustand((set, get) => { }, startProvision: (name = '', fromReset = false) => { get().dispatch.dynamic.cancel?.(true) - storeRegistry.getState('config').dispatch.setLoginError() - storeRegistry.getState('config').dispatch.resetRevokedSelf() - set(s => { + s.startProvisionTrigger++ s.username = name }) - const f = async () => { - // If we're logged in, we're coming from the user switcher; log out first to prevent the service from getting out of sync with the GUI about our logged-in-ness - if (storeRegistry.getState('config').loggedIn) { - await T.RPCGen.loginLogoutRpcPromise( - {force: false, keepSecrets: true}, - C.waitingKeyConfigLoginAsOther - ) - } - navigateAppend({props: {fromReset}, selected: 'username'}) - } - ignorePromise(f()) + navigateAppend({props: {fromReset}, selected: 'username'}) }, } diff --git a/shared/constants/push.d.ts b/shared/stores/push.d.ts similarity index 70% rename from shared/constants/push.d.ts rename to shared/stores/push.d.ts index ae8fd435e57a..29e8b1ff366f 100644 --- a/shared/constants/push.d.ts +++ b/shared/stores/push.d.ts @@ -1,4 +1,4 @@ -import type * as T from './types' +import type * as T from '@/constants/types' import type {UseBoundStore, StoreApi} from 'zustand' type Store = T.Immutable<{ @@ -10,6 +10,15 @@ type Store = T.Immutable<{ export type State = Store & { dispatch: { + defer: { + onGetDaemonHandshakeState?: () => T.Config.DaemonHandshakeState + onNavigateToThread?: ( + conversationIDKey: T.Chat.ConversationIDKey, + reason: 'push' | 'extension', + pushBody?: string + ) => void + onShowUserProfile?: (username: string) => void + } checkPermissions: () => Promise deleteToken: (version: number) => void handlePush: (notification: T.Push.PushNotification) => void diff --git a/shared/constants/push.desktop.tsx b/shared/stores/push.desktop.tsx similarity index 75% rename from shared/constants/push.desktop.tsx rename to shared/stores/push.desktop.tsx index b3fed7e84595..4a59a43a8a35 100644 --- a/shared/constants/push.desktop.tsx +++ b/shared/stores/push.desktop.tsx @@ -1,5 +1,5 @@ import * as Z from '@/util/zustand' -import {type Store, type State} from './push' +import {type Store, type State} from '@/stores/push' export const tokenType = '' @@ -15,6 +15,13 @@ export const usePushState = Z.createZustand(() => { checkPermissions: async () => { return Promise.resolve(false) }, + defer: { + onGetDaemonHandshakeState: () => { + return 'done' + }, + onNavigateToThread: () => {}, + onShowUserProfile: () => {}, + }, deleteToken: () => {}, handlePush: () => {}, initialPermissionsCheck: () => {}, diff --git a/shared/constants/push.native.tsx b/shared/stores/push.native.tsx similarity index 84% rename from shared/constants/push.native.tsx rename to shared/stores/push.native.tsx index 605401cfb53f..d780cd2cb941 100644 --- a/shared/constants/push.native.tsx +++ b/shared/stores/push.native.tsx @@ -1,13 +1,16 @@ -import * as Tabs from './tabs' -import * as S from './strings' -import {ignorePromise, neverThrowPromiseFunc, timeoutPromise} from './utils' -import {navigateAppend, navUpToScreen, switchTab} from './router2/util' -import {storeRegistry} from './store-registry' +import * as Tabs from '@/constants/tabs' +import * as S from '@/constants/strings' +import {ignorePromise, neverThrowPromiseFunc, timeoutPromise} from '@/constants/utils' +import {navigateAppend, navUpToScreen, switchTab} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useLogoutState} from '@/stores/logout' +import {useWaitingState} from '@/stores/waiting' import * as Z from '@/util/zustand' import logger from '@/logger' -import * as T from './types' +import * as T from '@/constants/types' import {isDevApplePushToken} from '@/local-debug' -import {isIOS} from './platform' +import {isIOS} from '@/constants/platform' import { checkPushPermissions, getRegistrationToken, @@ -15,7 +18,7 @@ import { requestPushPermissions, removeAllPendingNotificationRequests, } from 'react-native-kb' -import {type Store, type State} from './push' +import {type Store, type State} from '@/stores/push' export const tokenType = isIOS ? (isDevApplePushToken ? 'appledev' : 'apple') : 'androidplay' @@ -69,7 +72,7 @@ export const usePushState = Z.createZustand((set, get) => { const {conversationIDKey, unboxPayload, membersType} = notification - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('push', undefined, unboxPayload) + get().dispatch.defer.onNavigateToThread?.(conversationIDKey, 'push', unboxPayload) if (unboxPayload && membersType && !isIOS) { try { await T.RPCChat.localUnboxMobilePushNotificationRpcPromise({ @@ -112,12 +115,23 @@ export const usePushState = Z.createZustand((set, get) => { return false } }, + defer: { + onGetDaemonHandshakeState: () => { + throw new Error('onGetDaemonHandshakeState not implemented') + }, + onNavigateToThread: () => { + throw new Error('onNavigateToThread not implemented') + }, + onShowUserProfile: () => { + throw new Error('onShowUserProfile not implemented') + }, + }, deleteToken: version => { const f = async () => { const waitKey = 'push:deleteToken' - storeRegistry.getState('logout').dispatch.wait(waitKey, version, true) + useLogoutState.getState().dispatch.wait(waitKey, version, true) try { - const deviceID = storeRegistry.getState('current-user').deviceID + const deviceID = useCurrentUserState.getState().deviceID if (!deviceID) { logger.info('[PushToken] no device id') return @@ -133,7 +147,7 @@ export const usePushState = Z.createZustand((set, get) => { } catch (e) { logger.error('[PushToken] delete failed', e) } finally { - storeRegistry.getState('logout').dispatch.wait(waitKey, version, false) + useLogoutState.getState().dispatch.wait(waitKey, version, false) } } ignorePromise(f()) @@ -156,17 +170,17 @@ export const usePushState = Z.createZustand((set, get) => { // We only care if the user clicked while in session if (notification.userInteraction) { const {username} = notification - storeRegistry.getState('profile').dispatch.showUserProfile(username) + get().dispatch.defer.onShowUserProfile?.(username) } break case 'chat.extension': { const {conversationIDKey} = notification - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('extension') + get().dispatch.defer.onNavigateToThread?.(conversationIDKey, 'extension') } break case 'settings.contacts': - if (storeRegistry.getState('config').loggedIn) { + if (useConfigState.getState().loggedIn) { switchTab(Tabs.peopleTab) navUpToScreen('peopleRoot') } @@ -230,14 +244,14 @@ export const usePushState = Z.createZustand((set, get) => { const shownPushPrompt = await askNativeIfSystemPushPromptHasBeenShown() if (shownPushPrompt) { // we've already shown the prompt, take them to settings - storeRegistry.getState('config').dispatch.dynamic.openAppSettings?.() + useConfigState.getState().dispatch.defer.openAppSettings?.() get().dispatch.showPermissionsPrompt({persistSkip: true, show: false}) return } } try { - storeRegistry.getState('config').dispatch.dynamic.openAppSettings?.() - const {increment} = storeRegistry.getState('waiting').dispatch + useConfigState.getState().dispatch.defer.openAppSettings?.() + const {increment} = useWaitingState.getState().dispatch increment(S.waitingKeyPushPermissionsRequesting) await requestPermissionsFromNative() const permissions = await checkPermissionsFromNative() @@ -253,7 +267,7 @@ export const usePushState = Z.createZustand((set, get) => { }) } } finally { - const {decrement} = storeRegistry.getState('waiting').dispatch + const {decrement} = useWaitingState.getState().dispatch decrement(S.waitingKeyPushPermissionsRequesting) get().dispatch.showPermissionsPrompt({persistSkip: true, show: false}) } @@ -267,7 +281,7 @@ export const usePushState = Z.createZustand((set, get) => { }) const uploadPushToken = async () => { - const {deviceID, username} = storeRegistry.getState('current-user') + const {deviceID, username} = useCurrentUserState.getState() if (!username || !deviceID) { return } @@ -303,8 +317,8 @@ export const usePushState = Z.createZustand((set, get) => { // permissions checker finishes after the routeToInitialScreen is done. if ( p.show && - storeRegistry.getState('config').loggedIn && - storeRegistry.getState('daemon').handshakeState === 'done' && + useConfigState.getState().loggedIn && + get().dispatch.defer.onGetDaemonHandshakeState?.() === 'done' && !get().justSignedUp && !get().hasPermissions ) { diff --git a/shared/constants/recover-password/index.tsx b/shared/stores/recover-password.tsx similarity index 87% rename from shared/constants/recover-password/index.tsx rename to shared/stores/recover-password.tsx index 47d6eec523fa..6136a01a06ec 100644 --- a/shared/constants/recover-password/index.tsx +++ b/shared/stores/recover-password.tsx @@ -1,13 +1,13 @@ -import * as T from '../types' -import {ignorePromise, wrapErrors} from '../utils' -import {waitingKeyRecoverPassword} from '../strings' +import * as T from '@/constants/types' +import {ignorePromise, wrapErrors} from '@/constants/utils' +import {waitingKeyRecoverPassword} from '@/constants/strings' import * as Z from '@/util/zustand' import logger from '@/logger' import {RPCError} from '@/util/errors' -import {type Device} from '../provision' -import {rpcDeviceToDevice} from '../rpc-utils' -import {clearModals, navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {type Device} from '@/stores/provision' +import {rpcDeviceToDevice} from '@/constants/rpc-utils' +import {clearModals, navigateAppend, navigateUp} from '@/constants/router2' +import {useConfigState} from '@/stores/config' type Store = T.Immutable<{ devices: Array @@ -34,6 +34,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onProvisionCancel?: (ignoreWarning?: boolean) => void + onStartAccountReset?: (skipPassword: boolean, username: string) => void + } dynamic: { cancel?: () => void submitDeviceSelect?: (name: string) => void @@ -48,6 +52,14 @@ export interface State extends Store { export const useState = Z.createZustand((set, get) => { const dispatch: State['dispatch'] = { + defer: { + onProvisionCancel: () => { + throw new Error('onProvisionCancel not implemented') + }, + onStartAccountReset: () => { + throw new Error('onStartAccountReset not implemented') + }, + }, dynamic: { cancel: undefined, submitDeviceSelect: undefined, @@ -74,7 +86,7 @@ export const useState = Z.createZustand((set, get) => { const f = async () => { if (p.abortProvisioning) { - storeRegistry.getState('provision').dispatch.dynamic.cancel?.() + get().dispatch.defer.onProvisionCancel?.() } let hadError = false try { @@ -107,17 +119,13 @@ export const useState = Z.createZustand((set, get) => { } }) }) - storeRegistry - .getState('router') - .dispatch.navigateAppend('recoverPasswordDeviceSelector', !!replaceRoute) + navigateAppend('recoverPasswordDeviceSelector', !!replaceRoute) }, 'keybase.1.loginUi.promptPassphraseRecovery': () => {}, // This same RPC is called at the beginning and end of the 7-day wait by the service. 'keybase.1.loginUi.promptResetAccount': (params, response) => { if (params.prompt.t === T.RPCGen.ResetPromptType.enterResetPw) { - storeRegistry - .getState('router') - .dispatch.navigateAppend('recoverPasswordPromptResetPassword') + navigateAppend('recoverPasswordPromptResetPassword') const clear = () => { set(s => { s.dispatch.dynamic.submitResetPassword = undefined @@ -142,8 +150,7 @@ export const useState = Z.createZustand((set, get) => { }) }) } else { - const {startAccountReset} = storeRegistry.getState('autoreset').dispatch - startAccountReset(true, '') + get().dispatch.defer.onStartAccountReset?.(true, '') response.result(T.RPCGen.ResetPromptResponse.nothing) } }, @@ -227,14 +234,10 @@ export const useState = Z.createZustand((set, get) => { set(s => { s.error = msg }) - storeRegistry - .getState('router') - .dispatch.navigateAppend( - storeRegistry.getState('config').loggedIn - ? 'recoverPasswordErrorModal' - : 'recoverPasswordError', - true - ) + navigateAppend( + useConfigState.getState().loggedIn ? 'recoverPasswordErrorModal' : 'recoverPasswordError', + true + ) } } finally { set(s => { diff --git a/shared/stores/router2.tsx b/shared/stores/router2.tsx new file mode 100644 index 000000000000..0a1cf4cc0804 --- /dev/null +++ b/shared/stores/router2.tsx @@ -0,0 +1,90 @@ +import type * as T from '@/constants/types' +import * as Z from '@/util/zustand' +import type * as Tabs from '@/constants/tabs' +import type {RouteKeys} from '@/router-v2/route-params' +import * as Util from '@/constants/router2' + +export { + type NavState, + getTab, + navigationRef, + getRootState, + _getNavigator, + logState, + getVisiblePath, + getModalStack, + getVisibleScreen, + navToProfile, + navToThread, + getRouteTab, + getRouteLoggedIn, + useSafeFocusEffect, + makeScreen, +} from '@/constants/router2' +export type {PathParam, Navigator} from '@/constants/router2' + +type Store = T.Immutable<{ + navState?: unknown +}> + +const initialStore: Store = { + navState: undefined, +} + +export interface State extends Store { + dispatch: { + clearModals: () => void + defer: { + tabLongPress?: (tab: string) => void + } + navigateAppend: (path: Util.PathParam, replace?: boolean) => void + navigateUp: () => void + navUpToScreen: (name: RouteKeys) => void + popStack: () => void + resetState: () => void + setNavState: (ns: Util.NavState) => void + switchTab: (tab: Tabs.AppTab) => void + } + appendEncryptRecipientsBuilder: () => void + appendNewChatBuilder: () => void + appendNewTeamBuilder: (teamID: T.Teams.TeamID) => void + appendPeopleBuilder: () => void +} + +export const useRouterState = Z.createZustand((set, get) => { + const dispatch: State['dispatch'] = { + clearModals: Util.clearModals, + defer: { + tabLongPress: undefined, + }, + navUpToScreen: Util.navUpToScreen, + navigateAppend: Util.navigateAppend, + navigateUp: Util.navigateUp, + popStack: Util.popStack, + resetState: () => { + set(s => ({ + ...s, + dispatch: s.dispatch, + })) + }, + setNavState: next => { + const DEBUG_NAV = __DEV__ && (false as boolean) + DEBUG_NAV && console.log('[Nav] setNavState') + const prev = get().navState as Util.NavState + if (prev === next) return + set(s => { + s.navState = next + }) + }, + switchTab: Util.switchTab, + } + + return { + ...initialStore, + appendEncryptRecipientsBuilder: Util.appendEncryptRecipientsBuilder, + appendNewChatBuilder: Util.appendNewChatBuilder, + appendNewTeamBuilder: Util.appendNewTeamBuilder, + appendPeopleBuilder: Util.appendPeopleBuilder, + dispatch, + } +}) diff --git a/shared/constants/settings-chat/index.tsx b/shared/stores/settings-chat.tsx similarity index 91% rename from shared/constants/settings-chat/index.tsx rename to shared/stores/settings-chat.tsx index 24b2b4a1024f..71e6af5aca23 100644 --- a/shared/constants/settings-chat/index.tsx +++ b/shared/stores/settings-chat.tsx @@ -1,8 +1,8 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as S from '../strings' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' +import * as S from '@/constants/strings' import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' +import {useConfigState} from '@/stores/config' export type ChatUnfurlState = { unfurlMode?: T.RPCChat.UnfurlMode @@ -49,7 +49,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { const dispatch: State['dispatch'] = { contactSettingsRefresh: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -67,7 +67,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { }, contactSettingsSaved: (enabled, indirectFollowees, teamsEnabled, teamsList) => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } @@ -100,7 +100,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { resetState: 'default', unfurlSettingsRefresh: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -128,7 +128,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { s.unfurl = T.castDraft({unfurlError: undefined, unfurlMode, unfurlWhitelist}) }) const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { diff --git a/shared/constants/settings-contacts.d.ts b/shared/stores/settings-contacts.d.ts similarity index 95% rename from shared/constants/settings-contacts.d.ts rename to shared/stores/settings-contacts.d.ts index 3f48c273c201..2920c6643055 100644 --- a/shared/constants/settings-contacts.d.ts +++ b/shared/stores/settings-contacts.d.ts @@ -1,4 +1,4 @@ -import type * as T from './types' +import type * as T from '@/constants/types' import type {UseBoundStore, StoreApi} from 'zustand' type PermissionStatus = 'granted' | 'denied' | 'undetermined' | 'unknown' diff --git a/shared/constants/settings-contacts.desktop.tsx b/shared/stores/settings-contacts.desktop.tsx similarity index 100% rename from shared/constants/settings-contacts.desktop.tsx rename to shared/stores/settings-contacts.desktop.tsx diff --git a/shared/constants/settings-contacts.native.tsx b/shared/stores/settings-contacts.native.tsx similarity index 90% rename from shared/constants/settings-contacts.native.tsx rename to shared/stores/settings-contacts.native.tsx index 3d0f7da0407e..5043bf04d1f3 100644 --- a/shared/constants/settings-contacts.native.tsx +++ b/shared/stores/settings-contacts.native.tsx @@ -1,17 +1,19 @@ -import * as C from '.' import * as Contacts from 'expo-contacts' -import {ignorePromise} from './utils' -import * as T from './types' +import {ignorePromise} from '@/constants/utils' +import {importContactsWaitingKey} from '@/constants/strings' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import {addNotificationRequest} from 'react-native-kb' import logger from '@/logger' import type {Store, State} from './settings-contacts' import {RPCError} from '@/util/errors' import {getDefaultCountryCode} from 'react-native-kb' -import {getE164} from './settings-phone' +import {getE164} from '@/util/phone-numbers' import {pluralize} from '@/util/string' -import {navigateAppend} from './router2/util' -import {storeRegistry} from './store-registry' +import {navigateAppend} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useWaitingState} from '@/stores/waiting' const importContactsConfigKey = (username: string) => `ui.importContacts.${username}` @@ -80,14 +82,14 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { }) } const f = async () => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { logger.warn('no username') return } await T.RPCGen.configGuiSetValueRpcPromise( {path: importContactsConfigKey(username), value: {b: enable, isNull: false}}, - C.importContactsWaitingKey + importContactsWaitingKey ) get().dispatch.loadContactImportEnabled() } @@ -100,10 +102,10 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { }, loadContactImportEnabled: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { logger.warn('no username') return @@ -112,7 +114,7 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { try { const value = await T.RPCGen.configGuiGetValueRpcPromise( {path: importContactsConfigKey(username)}, - C.importContactsWaitingKey + importContactsWaitingKey ) enabled = !!value.b && !value.isNull } catch (error) { @@ -241,8 +243,8 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { }, requestPermissions: (thenToggleImportOn?: boolean, fromSettings?: boolean) => { const f = async () => { - const {decrement, increment} = storeRegistry.getState('waiting').dispatch - increment(C.importContactsWaitingKey) + const {decrement, increment} = useWaitingState.getState().dispatch + increment(importContactsWaitingKey) const status = (await Contacts.requestPermissionsAsync()).status if (status === Contacts.PermissionStatus.GRANTED && thenToggleImportOn) { @@ -251,7 +253,7 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { set(s => { s.permissionStatus = status }) - decrement(C.importContactsWaitingKey) + decrement(importContactsWaitingKey) } ignorePromise(f()) }, diff --git a/shared/constants/settings-email/index.tsx b/shared/stores/settings-email.tsx similarity index 97% rename from shared/constants/settings-email/index.tsx rename to shared/stores/settings-email.tsx index 7cb1d649961a..935f43208173 100644 --- a/shared/constants/settings-email/index.tsx +++ b/shared/stores/settings-email.tsx @@ -1,7 +1,7 @@ import * as Z from '@/util/zustand' -import {addEmailWaitingKey} from '../strings' -import {ignorePromise} from '../utils' -import * as T from '../types' +import {addEmailWaitingKey} from '@/constants/strings' +import {ignorePromise} from '@/constants/utils' +import * as T from '@/constants/types' import {isValidEmail} from '@/util/simple-validators' import {RPCError} from '@/util/errors' import logger from '@/logger' diff --git a/shared/constants/settings-invites/index.tsx b/shared/stores/settings-invites.tsx similarity index 95% rename from shared/constants/settings-invites/index.tsx rename to shared/stores/settings-invites.tsx index c6b6edc8e582..93e5df2833bf 100644 --- a/shared/constants/settings-invites/index.tsx +++ b/shared/stores/settings-invites.tsx @@ -1,11 +1,11 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import {waitingKeySettingsGeneric} from '../strings' +import {ignorePromise} from '@/constants/utils' +import {waitingKeySettingsGeneric} from '@/constants/strings' import {RPCError} from '@/util/errors' import logger from '@/logger' import trim from 'lodash/trim' -import * as T from '../types' -import {navigateAppend} from '../router2/util' +import * as T from '@/constants/types' +import {navigateAppend} from '@/constants/router2' type InviteBase = { id: string diff --git a/shared/constants/settings-notifications/index.tsx b/shared/stores/settings-notifications.tsx similarity index 97% rename from shared/constants/settings-notifications/index.tsx rename to shared/stores/settings-notifications.tsx index f93adaae9fdc..c22a90a587f0 100644 --- a/shared/constants/settings-notifications/index.tsx +++ b/shared/stores/settings-notifications.tsx @@ -1,10 +1,10 @@ import * as Z from '@/util/zustand' -import * as S from '../strings' -import {isAndroidNewerThanN} from '../platform' -import {ignorePromise, timeoutPromise} from '../utils' +import * as S from '@/constants/strings' +import {isAndroidNewerThanN} from '@/constants/platform' +import {ignorePromise, timeoutPromise} from '@/constants/utils' import {RPCError} from '@/util/errors' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' const securityGroup = 'security' const soundGroup = 'sound' diff --git a/shared/constants/settings-password/index.tsx b/shared/stores/settings-password.tsx similarity index 93% rename from shared/constants/settings-password/index.tsx rename to shared/stores/settings-password.tsx index ac305c0f6159..0b3c441f2422 100644 --- a/shared/constants/settings-password/index.tsx +++ b/shared/stores/settings-password.tsx @@ -1,11 +1,11 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import {waitingKeySettingsGeneric} from '../strings' +import {ignorePromise} from '@/constants/utils' +import {waitingKeySettingsGeneric} from '@/constants/strings' import logger from '@/logger' import {RPCError} from '@/util/errors' -import * as T from '../types' -import {navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import * as T from '@/constants/types' +import {navigateUp} from '@/constants/router2' +import {useLogoutState} from '@/stores/logout' type Store = T.Immutable<{ error: string @@ -141,7 +141,7 @@ export const usePWState = Z.createZustand((set, get) => { ) if (thenLogout) { - storeRegistry.getState('logout').dispatch.requestLogout() + useLogoutState.getState().dispatch.requestLogout() } navigateUp() } catch (error) { diff --git a/shared/constants/settings-phone/index.tsx b/shared/stores/settings-phone.tsx similarity index 88% rename from shared/constants/settings-phone/index.tsx rename to shared/stores/settings-phone.tsx index cc19e82aa27d..eefe6932475d 100644 --- a/shared/constants/settings-phone/index.tsx +++ b/shared/stores/settings-phone.tsx @@ -1,15 +1,10 @@ -import * as T from '../types' -import * as S from '../strings' -import {ignorePromise} from '../utils' +import * as T from '@/constants/types' +import * as S from '@/constants/strings' +import {ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' import logger from '@/logger' import {RPCError} from '@/util/errors' -import type { - e164ToDisplay as e164ToDisplayType, - phoneUtil as phoneUtilType, - ValidationResult as ValidationResultType, - PhoneNumberFormat as PhoneNumberFormatType, -} from '@/util/phone-numbers' +import type {e164ToDisplay as e164ToDisplayType} from '@/util/phone-numbers' export const makePhoneRow = (): PhoneRow => ({ displayNumber: '', @@ -19,25 +14,6 @@ export const makePhoneRow = (): PhoneRow => ({ verified: false, }) -// Get phone number in e.164, or null if we can't parse it. -export const getE164 = (phoneNumber: string, countryCode?: string) => { - const {phoneUtil, ValidationResult, PhoneNumberFormat} = require('@/util/phone-numbers') as { - phoneUtil: typeof phoneUtilType - ValidationResult: typeof ValidationResultType - PhoneNumberFormat: typeof PhoneNumberFormatType - } - try { - const parsed = countryCode ? phoneUtil.parse(phoneNumber, countryCode) : phoneUtil.parse(phoneNumber) - const reason = phoneUtil.isPossibleNumberWithReason(parsed) - if (reason !== ValidationResult.IS_POSSIBLE) { - return null - } - return phoneUtil.format(parsed, PhoneNumberFormat.E164) - } catch { - return null - } -} - const toPhoneRow = (p: T.RPCGen.UserPhoneNumber) => { const {e164ToDisplay} = require('@/util/phone-numbers') as {e164ToDisplay: typeof e164ToDisplayType} return { @@ -67,7 +43,7 @@ export const makePhoneError = (e: RPCError) => { } } -type PhoneRow = { +export type PhoneRow = { displayNumber: string e164: string searchable: boolean diff --git a/shared/constants/settings/index.tsx b/shared/stores/settings.tsx similarity index 70% rename from shared/constants/settings/index.tsx rename to shared/stores/settings.tsx index ce2327c8888d..6c9ed5b1a946 100644 --- a/shared/constants/settings/index.tsx +++ b/shared/stores/settings.tsx @@ -1,18 +1,20 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise} from '../utils' -import * as S from '../strings' -import {androidIsTestDevice, pprofDir} from '../platform' -import * as EngineGen from '@/actions/engine-gen-gen' +import * as T from '@/constants/types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' +import * as S from '@/constants/strings' +import {androidIsTestDevice, pprofDir} from '@/constants/platform' import openURL from '@/util/open-url' import * as Z from '@/util/zustand' import {RPCError} from '@/util/errors' -import * as Tabs from '../tabs' +import * as Tabs from '@/constants/tabs' import logger from '@/logger' -import {clearModals, navigateAppend, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {processorProfileInProgressKey, traceInProgressKey} from './util' +import {clearModals, navigateAppend, switchTab} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useWaitingState} from '@/stores/waiting' +import {processorProfileInProgressKey, traceInProgressKey} from '@/constants/settings' +import type {PhoneRow} from '@/stores/settings-phone' -export * from './util' +export * from '@/constants/settings' type Store = T.Immutable<{ checkPasswordIsCorrect?: boolean @@ -31,14 +33,18 @@ const initialStore: Store = { export interface State extends Store { dispatch: { checkPassword: (password: string) => void - dbNuke: () => void clearLogs: () => void + dbNuke: () => void + defer: { + getSettingsPhonePhones: () => undefined | ReadonlyMap + onSettingsEmailNotifyEmailsChanged: (list: ReadonlyArray) => void + onSettingsPhoneSetNumbers: (phoneNumbers?: ReadonlyArray) => void + } deleteAccountForever: (passphrase?: string) => void loadLockdownMode: () => void loadProxyData: () => void loadSettings: () => void loginBrowserViaWebAuthToken: () => void - onEngineIncomingImpl: (action: EngineGen.Actions) => void processorProfile: (durationSeconds: number) => void resetCheckPassword: () => void resetState: 'default' @@ -51,14 +57,14 @@ export interface State extends Store { } let maybeLoadAppLinkOnce = false -export const useSettingsState = Z.createZustand(set => { +export const useSettingsState = Z.createZustand((set, get) => { const maybeLoadAppLink = () => { - const phones = storeRegistry.getState('settings-phone').phones + const phones = get().dispatch.defer.getSettingsPhonePhones() if (!phones || phones.size > 0) { return } - if (maybeLoadAppLinkOnce || !storeRegistry.getState('config').startup.link.endsWith('/phone-app')) { + if (maybeLoadAppLinkOnce || !useConfigState.getState().startup.link.endsWith('/phone-app')) { return } maybeLoadAppLinkOnce = true @@ -95,9 +101,20 @@ export const useSettingsState = Z.createZustand(set => { } ignorePromise(f()) }, + defer: { + getSettingsPhonePhones: () => { + throw new Error('getSettingsPhonePhones not implemented') + }, + onSettingsEmailNotifyEmailsChanged: () => { + throw new Error('onSettingsEmailNotifyEmailsChanged not implemented') + }, + onSettingsPhoneSetNumbers: () => { + throw new Error('onSettingsPhoneSetNumbers not implemented') + }, + }, deleteAccountForever: passphrase => { const f = async () => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { throw new Error('Unable to delete account: no username set') @@ -108,7 +125,7 @@ export const useSettingsState = Z.createZustand(set => { } await T.RPCGen.loginAccountDeleteRpcPromise({passphrase}, S.waitingKeySettingsGeneric) - storeRegistry.getState('config').dispatch.setJustDeletedSelf(username) + useConfigState.getState().dispatch.setJustDeletedSelf(username) clearModals() navigateAppend(Tabs.loginTab) } @@ -116,7 +133,7 @@ export const useSettingsState = Z.createZustand(set => { }, loadLockdownMode: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -148,7 +165,7 @@ export const useSettingsState = Z.createZustand(set => { }, loadSettings: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -156,10 +173,8 @@ export const useSettingsState = Z.createZustand(set => { undefined, S.waitingKeySettingsLoadSettings ) - storeRegistry - .getState('settings-email') - .dispatch.notifyEmailAddressEmailsChanged(settings.emails ?? []) - storeRegistry.getState('settings-phone').dispatch.setNumbers(settings.phoneNumbers ?? undefined) + get().dispatch.defer.onSettingsEmailNotifyEmailsChanged(settings.emails ?? []) + get().dispatch.defer.onSettingsPhoneSetNumbers(settings.phoneNumbers ?? undefined) maybeLoadAppLink() } catch (error) { if (!(error instanceof RPCError)) { @@ -178,41 +193,13 @@ export const useSettingsState = Z.createZustand(set => { } ignorePromise(f()) }, - onEngineIncomingImpl: action => { - switch (action.type) { - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - logger.info('email verified') - storeRegistry - .getState('settings-email') - .dispatch.notifyEmailVerified(action.payload.params.emailAddress) - break - case EngineGen.keybase1NotifyUsersPasswordChanged: { - const randomPW = action.payload.params.state === T.RPCGen.PassphraseState.random - storeRegistry.getState('settings-password').dispatch.notifyUsersPasswordChanged(randomPW) - break - } - case EngineGen.keybase1NotifyPhoneNumberPhoneNumbersChanged: { - const {list} = action.payload.params - storeRegistry - .getState('settings-phone') - .dispatch.notifyPhoneNumberPhoneNumbersChanged(list ?? undefined) - break - } - case EngineGen.keybase1NotifyEmailAddressEmailsChanged: { - const list = action.payload.params.list ?? [] - storeRegistry.getState('settings-email').dispatch.notifyEmailAddressEmailsChanged(list) - break - } - default: - } - }, processorProfile: profileDurationSeconds => { const f = async () => { await T.RPCGen.pprofLogProcessorProfileRpcPromise({ logDirForMobile: pprofDir, profileDurationSeconds, }) - const {decrement, increment} = storeRegistry.getState('waiting').dispatch + const {decrement, increment} = useWaitingState.getState().dispatch increment(processorProfileInProgressKey) await timeoutPromise(profileDurationSeconds * 1_000) decrement(processorProfileInProgressKey) @@ -232,7 +219,7 @@ export const useSettingsState = Z.createZustand(set => { }, setLockdownMode: enabled => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -270,7 +257,7 @@ export const useSettingsState = Z.createZustand(set => { logDirForMobile: pprofDir, traceDurationSeconds: durationSeconds, }) - const {decrement, increment} = storeRegistry.getState('waiting').dispatch + const {decrement, increment} = useWaitingState.getState().dispatch increment(traceInProgressKey) await timeoutPromise(durationSeconds * 1_000) decrement(traceInProgressKey) diff --git a/shared/constants/signup/index.tsx b/shared/stores/signup.tsx similarity index 90% rename from shared/constants/signup/index.tsx rename to shared/stores/signup.tsx index d5526f28da51..3d35852a6978 100644 --- a/shared/constants/signup/index.tsx +++ b/shared/stores/signup.tsx @@ -1,15 +1,15 @@ -import * as Platforms from '../platform' -import {ignorePromise} from '../utils' -import * as S from '../strings' +import * as Platforms from '@/constants/platform' +import {ignorePromise} from '@/constants/utils' +import * as S from '@/constants/strings' import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import logger from '@/logger' import trim from 'lodash/trim' import {RPCError} from '@/util/errors' import {isValidEmail, isValidName, isValidUsername} from '@/util/simple-validators' -import {navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navigateUp} from '@/constants/router2' +import {useConfigState} from '@/stores/config' type Store = T.Immutable<{ devicename: string @@ -45,6 +45,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onEditEmail?: (p: {email: string; makeSearchable: boolean}) => void + onShowPermissionsPrompt?: (p: {justSignedUp?: boolean}) => void + } checkDeviceName: (devicename: string) => void checkInviteCode: () => void checkUsername: (username: string) => void @@ -80,7 +84,7 @@ export const useSignupState = Z.createZustand((set, get) => { } try { - storeRegistry.getState('push').dispatch.showPermissionsPrompt({justSignedUp: true}) + get().dispatch.defer.onShowPermissionsPrompt?.({justSignedUp: true}) await T.RPCGen.signupSignupRpcListener({ customResponseIncomingCallMap: { @@ -121,7 +125,7 @@ export const useSignupState = Z.createZustand((set, get) => { } // If the email was set to be visible during signup, we need to set that with a separate RPC. if (noErrors() && get().emailVisible) { - storeRegistry.getState('settings-email').dispatch.editEmail({email: get().email, makeSearchable: true}) + get().dispatch.defer.onEditEmail?.({email: get().email, makeSearchable: true}) } } catch (_error) { if (_error instanceof RPCError) { @@ -130,7 +134,7 @@ export const useSignupState = Z.createZustand((set, get) => { s.signupError = error }) navigateAppend('signupError') - storeRegistry.getState('push').dispatch.showPermissionsPrompt({justSignedUp: false}) + get().dispatch.defer.onShowPermissionsPrompt?.({justSignedUp: false}) } } } @@ -228,6 +232,14 @@ export const useSignupState = Z.createZustand((set, get) => { s.justSignedUpEmail = '' }) }, + defer: { + onEditEmail: () => { + throw new Error('onEditEmail not implemented') + }, + onShowPermissionsPrompt: () => { + throw new Error('onShowPermissionsPrompt not implemented') + }, + }, goBackAndClearErrors: () => { set(s => { s.devicenameError = '' @@ -255,7 +267,7 @@ export const useSignupState = Z.createZustand((set, get) => { }) const f = async () => { // If we're logged in, we're coming from the user switcher; log out first to prevent the service from getting out of sync with the GUI about our logged-in-ness - if (storeRegistry.getState('config').loggedIn) { + if (useConfigState.getState().loggedIn) { await T.RPCGen.loginLogoutRpcPromise({force: false, keepSecrets: true}) } try { diff --git a/shared/stores/store-registry.tsx b/shared/stores/store-registry.tsx new file mode 100644 index 000000000000..dbae6e710f48 --- /dev/null +++ b/shared/stores/store-registry.tsx @@ -0,0 +1,157 @@ +// used to allow non-circular cross-calls between stores +// ONLY for zustand stores +import type * as T from '@/constants/types' +import type * as ConvoStateType from '@/stores/convostate' +import type {ConvoState} from '@/stores/convostate' +import type {State as ChatState, useChatState} from '@/stores/chat2' +import type {State as DaemonState, useDaemonState} from '@/stores/daemon' +import type {State as FSState, useFSState} from '@/stores/fs' +import type {State as PeopleState, usePeopleState} from '@/stores/people' +import type {State as ProfileState, useProfileState} from '@/stores/profile' +import type {State as ProvisionState, useProvisionState} from '@/stores/provision' +import type {State as PushState, usePushState} from '@/stores/push' +import type { + State as RecoverPasswordState, + useState as useRecoverPasswordState, +} from '@/stores/recover-password' +import type {State as SettingsState, useSettingsState} from '@/stores/settings' +import type {State as SettingsEmailState, useSettingsEmailState} from '@/stores/settings-email' +import type {State as SettingsPhoneState, useSettingsPhoneState} from '@/stores/settings-phone' +import type {State as SignupState, useSignupState} from '@/stores/signup' +import type {State as TeamsState, useTeamsState} from '@/stores/teams' +import type {State as Tracker2State, useTrackerState} from '@/stores/tracker2' +import type {State as UsersState, useUsersState} from '@/stores/users' + +type StoreName = + | 'chat' + | 'daemon' + | 'fs' + | 'people' + | 'profile' + | 'provision' + | 'push' + | 'recover-password' + | 'settings' + | 'settings-email' + | 'settings-phone' + | 'signup' + | 'teams' + | 'tracker2' + | 'users' + +type StoreStates = { + chat: ChatState + daemon: DaemonState + fs: FSState + people: PeopleState + profile: ProfileState + provision: ProvisionState + push: PushState + 'recover-password': RecoverPasswordState + settings: SettingsState + 'settings-email': SettingsEmailState + 'settings-phone': SettingsPhoneState + signup: SignupState + teams: TeamsState + tracker2: Tracker2State + users: UsersState +} + +type StoreHooks = { + chat: typeof useChatState + daemon: typeof useDaemonState + fs: typeof useFSState + people: typeof usePeopleState + profile: typeof useProfileState + provision: typeof useProvisionState + push: typeof usePushState + 'recover-password': typeof useRecoverPasswordState + settings: typeof useSettingsState + 'settings-email': typeof useSettingsEmailState + 'settings-phone': typeof useSettingsPhoneState + signup: typeof useSignupState + teams: typeof useTeamsState + tracker2: typeof useTrackerState + users: typeof useUsersState +} + +class StoreRegistry { + getStore(storeName: T): StoreHooks[T] { + /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */ + switch (storeName) { + case 'chat': { + const {useChatState} = require('@/stores/chat2') + return useChatState + } + case 'daemon': { + const {useDaemonState} = require('@/stores/daemon') + return useDaemonState + } + case 'fs': { + const {useFSState} = require('@/stores/fs') + return useFSState + } + case 'people': { + const {usePeopleState} = require('@/stores/people') + return usePeopleState + } + case 'profile': { + const {useProfileState} = require('@/stores/profile') + return useProfileState + } + case 'provision': { + const {useProvisionState} = require('@/stores/provision') + return useProvisionState + } + case 'push': { + const {usePushState} = require('@/stores/push') + return usePushState + } + case 'recover-password': { + const {useState} = require('@/stores/recover-password') + return useState + } + case 'settings': { + const {useSettingsState} = require('@/stores/settings') + return useSettingsState + } + case 'settings-email': { + const {useSettingsEmailState} = require('@/stores/settings-email') + return useSettingsEmailState + } + case 'settings-phone': { + const {useSettingsPhoneState} = require('@/stores/settings-phone') + return useSettingsPhoneState + } + case 'signup': { + const {useSignupState} = require('@/stores/signup') + return useSignupState + } + case 'teams': { + const {useTeamsState} = require('@/stores/teams') + return useTeamsState + } + case 'tracker2': { + const {useTrackerState} = require('@/stores/tracker2') + return useTrackerState + } + case 'users': { + const {useUsersState} = require('@/stores/users') + return useUsersState + } + default: + throw new Error(`Unknown store: ${storeName}`) + } + } + + getState(storeName: T): StoreStates[T] { + return this.getStore(storeName).getState() as StoreStates[T] + } + + getConvoState(id: T.Chat.ConversationIDKey): ConvoState { + const {getConvoState} = require('@/stores/convostate') as typeof ConvoStateType + return getConvoState(id) + } +} + +export const storeRegistry = new StoreRegistry() diff --git a/shared/constants/team-building/index.tsx b/shared/stores/team-building.tsx similarity index 82% rename from shared/constants/team-building/index.tsx rename to shared/stores/team-building.tsx index 0d838f7c2332..fb68799d16c0 100644 --- a/shared/constants/team-building/index.tsx +++ b/shared/stores/team-building.tsx @@ -1,6 +1,5 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as Router2 from '../router2' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' import * as React from 'react' import * as Z from '@/util/zustand' import logger from '@/logger' @@ -11,10 +10,10 @@ import {serviceIdFromString} from '@/util/platforms' import {type StoreApi, type UseBoundStore, useStore} from 'zustand' import {validateEmailAddress} from '@/util/email-address' import {registerDebugClear} from '@/util/debug' -import {searchWaitingKey} from './utils' -import {navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' -export {allServices, selfToUser, searchWaitingKey} from './utils' +import {searchWaitingKey} from '@/constants/strings' +import {navigateUp, getModalStack} from '@/constants/router2' +export {allServices, selfToUser} from '@/constants/team-building' +export {searchWaitingKey} from '@/constants/strings' type Store = T.Immutable<{ namespace: T.TB.AllowedNamespace @@ -54,6 +53,16 @@ export interface State extends Store { cancelTeamBuilding: () => void changeSendNotification: (sendNotification: boolean) => void closeTeamBuilding: () => void + defer: { + onAddMembersWizardPushMembers: (members: Array) => void + onFinishedTeamBuildingChat: (users: ReadonlySet) => void + onFinishedTeamBuildingCrypto: (users: ReadonlySet) => void + onGetSettingsContactsImportEnabled: () => boolean | undefined + onGetSettingsContactsUserCountryCode: () => string | undefined + onShowUserProfile: (username: string) => void + onUsersGetBlockState: (usernames: ReadonlyArray) => void + onUsersUpdates: (infos: ReadonlyArray<{name: string; info: Partial}>) => void + } fetchUserRecs: () => void finishTeamBuilding: () => void finishedTeamBuilding: () => void @@ -271,7 +280,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { // we want the first item for (const user of teamSoFar) { const username = user.serviceMap.keybase || user.id - storeRegistry.getState('profile').dispatch.showUserProfile(username) + get().dispatch.defer.onShowUserProfile(username) break } }, 100) @@ -291,13 +300,39 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }) }, closeTeamBuilding: () => { - const modals = Router2.getModalStack() + const modals = getModalStack() const routeNames = [...namespaceToRoute.values()] const routeName = modals.at(-1)?.name if (routeNames.includes(routeName ?? '')) { navigateUp() } }, + defer: { + onAddMembersWizardPushMembers: (_members: Array) => { + throw new Error('onAddMembersWizardPushMembers not properly initialized') + }, + onFinishedTeamBuildingChat: (_users: ReadonlySet) => { + throw new Error('onFinishedTeamBuildingChat not properly initialized') + }, + onFinishedTeamBuildingCrypto: (_users: ReadonlySet) => { + throw new Error('onFinishedTeamBuildingCrypto not properly initialized') + }, + onGetSettingsContactsImportEnabled: () => { + throw new Error('onGetSettingsContactsImportEnabled not properly initialized') + }, + onGetSettingsContactsUserCountryCode: () => { + throw new Error('onGetSettingsContactsUserCountryCode not properly initialized') + }, + onShowUserProfile: (_username: string) => { + throw new Error('onShowUserProfile not properly initialized') + }, + onUsersGetBlockState: (_usernames: ReadonlyArray) => { + throw new Error('onUsersGetBlockState not properly initialized') + }, + onUsersUpdates: (_infos: ReadonlyArray<{name: string; info: Partial}>) => { + throw new Error('onUsersUpdates not properly initialized') + }, + }, fetchUserRecs: () => { const includeContacts = get().namespace === 'chat2' const f = async () => { @@ -313,7 +348,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const contacts = contactRes.map(contactToUser) let suggestions = suggestionRes.map(interestingPersonToUser) const expectingContacts = - storeRegistry.getState('settings-contacts').importEnabled && includeContacts + get().dispatch.defer.onGetSettingsContactsImportEnabled() && includeContacts if (expectingContacts) { suggestions = suggestions.slice(0, 10) } @@ -336,11 +371,9 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { get().dispatch.closeTeamBuilding() const {teamSoFar} = get() if (get().namespace === 'teams') { - storeRegistry - .getState('teams') - .dispatch.addMembersWizardPushMembers( - [...teamSoFar].map(user => ({assertion: user.id, role: 'writer'})) - ) + get().dispatch.defer.onAddMembersWizardPushMembers( + [...teamSoFar].map(user => ({assertion: user.id, role: 'writer'})) + ) get().dispatch.finishedTeamBuilding() } }, @@ -360,11 +393,11 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const {finishedTeam, namespace} = get() switch (namespace) { case 'crypto': { - storeRegistry.getState('crypto').dispatch.onTeamBuildingFinished(finishedTeam) + get().dispatch.defer.onFinishedTeamBuildingCrypto(finishedTeam) break } case 'chat2': { - storeRegistry.getState('chat').dispatch.onTeamBuildingFinished(finishedTeam) + get().dispatch.defer.onFinishedTeamBuildingChat(finishedTeam) break } default: @@ -415,7 +448,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { let users: typeof _users if (selectedService === 'keybase') { // If we are on Keybase tab, do additional search if query is phone/email. - const userRegion = storeRegistry.getState('settings-contacts').userCountryCode + const userRegion = get().dispatch.defer.onGetSettingsContactsUserCountryCode() users = await specialContactSearch(_users, searchQuery, userRegion) } else { users = _users @@ -432,7 +465,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } return arr }, new Array<{info: {fullname: string}; name: string}>()) - storeRegistry.getState('users').dispatch.updates(updates) + get().dispatch.defer.onUsersUpdates(updates) const blocks = users.reduce((arr, {serviceMap}) => { const {keybase} = serviceMap if (keybase) { @@ -440,7 +473,9 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } return arr }, new Array()) - blocks.length && storeRegistry.getState('users').dispatch.getBlockState(blocks) + if (blocks.length) { + get().dispatch.defer.onUsersGetBlockState(blocks) + } } ignorePromise(f()) }, @@ -477,6 +512,9 @@ export const createTBStore = (namespace: T.TB.AllowedNamespace) => { return next } +export const getTBStore = (namespace: T.TB.AllowedNamespace): State => + createTBStore(namespace).getState() + const Context = React.createContext(null) type TBProviderProps = React.PropsWithChildren<{namespace: T.TB.AllowedNamespace}> diff --git a/shared/constants/teams/index.tsx b/shared/stores/teams.tsx similarity index 96% rename from shared/constants/teams/index.tsx rename to shared/stores/teams.tsx index d221afa1fddd..ac4b61953748 100644 --- a/shared/constants/teams/index.tsx +++ b/shared/stores/teams.tsx @@ -1,6 +1,6 @@ -import * as S from '../strings' -import {ignorePromise, wrapErrors} from '../utils' -import * as T from '../types' +import * as S from '@/constants/strings' +import {ignorePromise, wrapErrors} from '@/constants/utils' +import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import { getVisibleScreen, @@ -9,19 +9,23 @@ import { navigateUp, navUpToScreen, navToProfile, -} from '../router2/util' +} from '@/constants/router2' import * as Z from '@/util/zustand' import invert from 'lodash/invert' import logger from '@/logger' import openSMS from '@/util/sms' import {RPCError, logError} from '@/util/errors' -import {isMobile, isPhone} from '../platform' +import {isMobile, isPhone} from '@/constants/platform' import {mapGetEnsureValue} from '@/util/map' -import {bodyToJSON} from '../rpc-utils' +import {bodyToJSON} from '@/constants/rpc-utils' import {fixCrop} from '@/util/crop' -import {storeRegistry} from '../store-registry' -import * as Util from './util' -import {getTab} from '../router2/util' +import {getTBStore} from '@/stores/team-building' +import {storeRegistry} from '@/stores/store-registry' +import {useConfigState} from '@/stores/config' +import {type useChatState} from '@/stores/chat2' +import {useCurrentUserState} from '@/stores/current-user' +import * as Util from '@/constants/teams' +import {getTab} from '@/constants/router2' export { baseRetentionPolicies, @@ -31,7 +35,7 @@ export { teamRoleByEnum, retentionPolicyToServiceRetentionPolicy, userIsRoleInTeamWithInfo, -} from './util' +} from '@/constants/teams' export const teamRoleTypes = ['reader', 'writer', 'admin', 'owner'] as const @@ -304,7 +308,7 @@ export const getDisabledReasonsForRolePicker = ( theyAreOwner = membersToModify.some(username => members.get(username)?.type === 'owner') } - const myUsername = storeRegistry.getState('current-user').username + const myUsername = useCurrentUserState.getState().username const you = members.get(myUsername) // Fallback to the lowest role, although this shouldn't happen const yourRole = you?.type ?? 'reader' @@ -862,6 +866,13 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onChatNavigateToInbox?: (allowSwitchTab?: boolean) => void + onChatPreviewConversation?: ( + p: Parameters['dispatch']['previewConversation']>[0] + ) => void + onUsersUpdates?: (updates: ReadonlyArray<{name: string; info: Partial}>) => void + } dynamic: { respondToInviteLink?: (accept: boolean) => void } @@ -1044,7 +1055,7 @@ export interface State extends Store { sendChatNotification: boolean, crop?: T.RPCGen.ImageCropRect ) => void - updateTeamRetentionPolicy: (metas: Array) => void + updateTeamRetentionPolicy: (metas: ReadonlyArray) => void } } @@ -1220,7 +1231,7 @@ export const useTeamsState = Z.createZustand((set, get) => { ) if (res.notAdded && res.notAdded.length > 0) { const usernames = res.notAdded.map(elem => elem.username) - storeRegistry.getTBStore('teams').dispatch.finishedTeamBuilding() + getTBStore('teams').dispatch.finishedTeamBuilding() navigateAppend({ props: {source: 'teamAddSomeFailed', usernames}, selected: 'contactRestricted', @@ -1232,7 +1243,7 @@ export const useTeamsState = Z.createZustand((set, get) => { s.errorInAddToTeam = '' }) if (fromTeamBuilder) { - storeRegistry.getTBStore('teams').dispatch.finishedTeamBuilding() + getTBStore('teams').dispatch.finishedTeamBuilding() } } catch (error) { if (!(error instanceof RPCError)) { @@ -1244,7 +1255,7 @@ export const useTeamsState = Z.createZustand((set, get) => { ?.filter(elem => elem?.key === 'usernames') .map(elem => elem?.value) const usernames = users?.[0]?.split(',') ?? [] - storeRegistry.getTBStore('teams').dispatch.finishedTeamBuilding() + getTBStore('teams').dispatch.finishedTeamBuilding() navigateAppend({ props: {source: 'teamAddAllFailed', usernames}, selected: 'contactRestricted', @@ -1258,7 +1269,7 @@ export const useTeamsState = Z.createZustand((set, get) => { }) // TODO this should not error on member already in team if (fromTeamBuilder) { - storeRegistry.getTBStore('teams').dispatch.setError(msg) + getTBStore('teams').dispatch.setError(msg) } } } @@ -1423,7 +1434,7 @@ export const useTeamsState = Z.createZustand((set, get) => { get().dispatch.loadTeamChannelList(teamID) // Select the new channel, and switch to the chat tab. if (navToChatOnSuccess) { - storeRegistry.getState('chat').dispatch.previewConversation({ + get().dispatch.defer.onChatPreviewConversation?.({ channelname, conversationIDKey: newConversationIDKey, reason: 'newChannel', @@ -1493,16 +1504,17 @@ export const useTeamsState = Z.createZustand((set, get) => { if (fromChat) { clearModals() - const {previewConversation, navigateToInbox} = storeRegistry.getState('chat').dispatch - navigateToInbox() - previewConversation({channelname: 'general', reason: 'convertAdHoc', teamname}) + get().dispatch.defer.onChatNavigateToInbox?.() + get().dispatch.defer.onChatPreviewConversation?.({ + channelname: 'general', + reason: 'convertAdHoc', + teamname, + }) } else { clearModals() navigateAppend({props: {teamID}, selected: 'team'}) if (isMobile) { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {createdTeam: true, teamID}, selected: 'profileEditAvatar'}) + navigateAppend({props: {createdTeam: true, teamID}, selected: 'profileEditAvatar'}) } } } catch (error) { @@ -1519,7 +1531,7 @@ export const useTeamsState = Z.createZustand((set, get) => { set(s => { s.errorInTeamCreation = '' }) - const me = storeRegistry.getState('current-user').username + const me = useCurrentUserState.getState().username const participantInfo = storeRegistry.getConvoState(conversationIDKey).participants // exclude bots from the newly created team, they can be added back later. const participants = participantInfo.name.filter(p => p !== me) // we will already be in as 'owner' @@ -1529,6 +1541,22 @@ export const useTeamsState = Z.createZustand((set, get) => { })) get().dispatch.createNewTeam(teamname, false, true, {sendChatNotification: true, users}) }, + defer: { + onChatNavigateToInbox: (_allowSwitchTab?: boolean) => { + throw new Error('onChatNavigateToInbox not implemented') + }, + onChatPreviewConversation: (_p: { + channelname?: string + conversationIDKey?: T.Chat.ConversationIDKey + reason?: string + teamname?: string + }) => { + throw new Error('onChatPreviewConversation not implemented') + }, + onUsersUpdates: (_updates: ReadonlyArray<{name: string; info: Partial}>) => { + throw new Error('onUsersUpdates not implemented') + }, + }, deleteChannelConfirmed: (teamID, conversationIDKey) => { const f = async () => { // channelName is only needed for confirmation, so since we handle @@ -1725,7 +1753,7 @@ export const useTeamsState = Z.createZustand((set, get) => { set(s => { s.teamIDToMembers.set(teamID, members) }) - storeRegistry.getState('users').dispatch.updates( + get().dispatch.defer.onUsersUpdates?.( [...members.values()].map(m => ({ info: {fullname: m.fullName}, name: m.username, @@ -1790,8 +1818,8 @@ export const useTeamsState = Z.createZustand((set, get) => { } const f = async () => { - const username = storeRegistry.getState('current-user').username - const loggedIn = storeRegistry.getState('config').loggedIn + const username = useCurrentUserState.getState().username + const loggedIn = useConfigState.getState().loggedIn if (!username || !loggedIn) { logger.warn('getTeams while logged out') return @@ -2029,9 +2057,7 @@ export const useTeamsState = Z.createZustand((set, get) => { ) logger.info(`leaveTeam: left ${teamname} successfully`) clearModals() - storeRegistry - .getState('router') - .dispatch.navUpToScreen(context === 'chat' ? 'chatRoot' : 'teamsRoot') + navUpToScreen(context === 'chat' ? 'chatRoot' : 'teamsRoot') get().dispatch.getTeams() } catch (error) { if (error instanceof RPCError) { @@ -2163,9 +2189,7 @@ export const useTeamsState = Z.createZustand((set, get) => { }) }, manageChatChannels: teamID => { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {teamID}, selected: 'teamAddToChannels'}) + navigateAppend({props: {teamID}, selected: 'teamAddToChannels'}) }, notifyTeamTeamRoleMapChanged: (newVersion: number) => { const loadedVersion = get().teamRoleMap.loadedVersion @@ -2307,7 +2331,7 @@ export const useTeamsState = Z.createZustand((set, get) => { break case EngineGen.keybase1NotifyBadgesBadgeState: { const {badgeState} = action.payload.params - const loggedIn = storeRegistry.getState('config').loggedIn + const loggedIn = useConfigState.getState().loggedIn if (loggedIn) { const deletedTeams = badgeState.deletedTeams || [] const newTeams = new Set(badgeState.newTeams || []) @@ -2538,14 +2562,14 @@ export const useTeamsState = Z.createZustand((set, get) => { const convID = T.Chat.keyToConversationID(conversationIDKey) await T.RPCChat.localJoinConversationByIDLocalRpcPromise({convID}, waitingKey) } catch (error) { - storeRegistry.getState('config').dispatch.setGlobalError(error) + useConfigState.getState().dispatch.setGlobalError(error) } } else { try { const convID = T.Chat.keyToConversationID(conversationIDKey) await T.RPCChat.localLeaveConversationLocalRpcPromise({convID}, waitingKey) } catch (error) { - storeRegistry.getState('config').dispatch.setGlobalError(error) + useConfigState.getState().dispatch.setGlobalError(error) } } } @@ -2658,14 +2682,14 @@ export const useTeamsState = Z.createZustand((set, get) => { teamID, }) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (ignoreAccessRequests !== settings.ignoreAccessRequests) { try { await T.RPCGen.teamsSetTarsDisabledRpcPromise({disabled: settings.ignoreAccessRequests, teamID}) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (publicityAnyMember !== settings.publicityAnyMember) { @@ -2675,7 +2699,7 @@ export const useTeamsState = Z.createZustand((set, get) => { teamID, }) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (publicityMember !== settings.publicityMember) { @@ -2685,14 +2709,14 @@ export const useTeamsState = Z.createZustand((set, get) => { teamID, }) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (publicityTeam !== settings.publicityTeam) { try { await T.RPCGen.teamsSetTeamShowcaseRpcPromise({isShowcased: settings.publicityTeam, teamID}) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } } @@ -2871,13 +2895,9 @@ export const useTeamsState = Z.createZustand((set, get) => { logger.info(`team="${teamname}" cannot be loaded:`, err) // navigate to team page for team we're not in logger.info(`showing external team page, join=${join}`) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {teamname}, selected: 'teamExternalTeam'}) + navigateAppend({props: {teamname}, selected: 'teamExternalTeam'}) if (join) { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {initialTeamname: teamname}, selected: 'teamJoinTeamDialog'}) + navigateAppend({props: {initialTeamname: teamname}, selected: 'teamJoinTeamDialog'}) } return } @@ -2899,9 +2919,7 @@ export const useTeamsState = Z.createZustand((set, get) => { return } } - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {initialTab, teamID}, selected: 'team'}) + navigateAppend({props: {initialTab, teamID}, selected: 'team'}) if (addMembers) { navigateAppend({ props: {namespace: 'teams', teamID, title: ''}, diff --git a/shared/constants/tracker2/index.tsx b/shared/stores/tracker2.tsx similarity index 93% rename from shared/constants/tracker2/index.tsx rename to shared/stores/tracker2.tsx index 8ea35a1b511e..8b9475cb04a8 100644 --- a/shared/constants/tracker2/index.tsx +++ b/shared/stores/tracker2.tsx @@ -1,13 +1,13 @@ -import * as S from '../strings' +import * as S from '@/constants/strings' import * as EngineGen from '@/actions/engine-gen-gen' -import {generateGUIID, ignorePromise} from '../utils' +import {generateGUIID, ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import {RPCError} from '@/util/errors' import {mapGetEnsureValue} from '@/util/map' -import {navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navigateUp} from '@/constants/router2' +import {useCurrentUserState} from '@/stores/current-user' export const noDetails: T.Tracker.Details = { assertions: new Map(), @@ -163,6 +163,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onShowUserProfile?: (username: string) => void + onUsersUpdates?: (updates: ReadonlyArray<{name: string; info: Partial}>) => void + } changeFollow: (guiID: string, follow: boolean) => void closeTracker: (guiID: string) => void getProofSuggestions: () => void @@ -227,6 +231,14 @@ export const useTrackerState = Z.createZustand((set, get) => { s.showTrackerSet.delete(username) }) }, + defer: { + onShowUserProfile: () => { + throw new Error('onShowUserProfile not implemented') + }, + onUsersUpdates: () => { + throw new Error('onUsersUpdates not implemented') + }, + }, getProofSuggestions: () => { const f = async () => { try { @@ -290,13 +302,13 @@ export const useTrackerState = Z.createZustand((set, get) => { } else if (error.code === T.RPCGen.StatusCode.scnotfound) { // we're on the profile page for a user that does not exist. Currently the only way // to get here is with an invalid link or deeplink. - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - `You followed a profile link for a user (${assertion}) that does not exist.` - ) navigateUp() - navigateAppend('keybaseLinkError') + navigateAppend({ + props: { + error: `You followed a profile link for a user (${assertion}) that does not exist.`, + }, + selected: 'keybaseLinkError', + }) } // hooked into reloadable logger.error(`Error loading profile: ${error.message}`) @@ -319,9 +331,9 @@ export const useTrackerState = Z.createZustand((set, get) => { d.followersCount = d.followers.size }) if (fs.users) { - storeRegistry - .getState('users') - .dispatch.updates(fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username}))) + get().dispatch.defer.onUsersUpdates?.( + fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username})) + ) } } catch (error) { if (error instanceof RPCError) { @@ -345,9 +357,9 @@ export const useTrackerState = Z.createZustand((set, get) => { d.followingCount = d.following.size }) if (fs.users) { - storeRegistry - .getState('users') - .dispatch.updates(fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username}))) + get().dispatch.defer.onUsersUpdates?.( + fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username})) + ) } } catch (error) { if (error instanceof RPCError) { @@ -435,8 +447,7 @@ export const useTrackerState = Z.createZustand((set, get) => { ) d.hidFromFollowers = hidFromFollowers }) - username && - storeRegistry.getState('users').dispatch.updates([{info: {fullname: card.fullName}, name: username}]) + username && get().dispatch.defer.onUsersUpdates?.([{info: {fullname: card.fullName}, name: username}]) }, notifyReset: guiID => { set(s => { @@ -531,11 +542,11 @@ export const useTrackerState = Z.createZustand((set, get) => { } // if we mutated somehow reload ourselves and reget the suggestions case EngineGen.keybase1NotifyUsersUserChanged: { - if (storeRegistry.getState('current-user').uid !== action.payload.params.uid) { + if (useCurrentUserState.getState().uid !== action.payload.params.uid) { return } get().dispatch.load({ - assertion: storeRegistry.getState('current-user').username, + assertion: useCurrentUserState.getState().username, forceDisplay: false, fromDaemon: false, guiID: generateGUIID(), @@ -595,7 +606,7 @@ export const useTrackerState = Z.createZustand((set, get) => { }) if (!skipNav) { // go to profile page - storeRegistry.getState('profile').dispatch.showUserProfile(username) + get().dispatch.defer.onShowUserProfile?.(username) } }, updateResult: (guiID, result, reason) => { diff --git a/shared/constants/unlock-folders/index.tsx b/shared/stores/unlock-folders.tsx similarity index 86% rename from shared/constants/unlock-folders/index.tsx rename to shared/stores/unlock-folders.tsx index dc4a1d43ca00..d8fa1d8dd573 100644 --- a/shared/constants/unlock-folders/index.tsx +++ b/shared/stores/unlock-folders.tsx @@ -1,10 +1,9 @@ import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import logger from '@/logger' import {getEngine} from '@/engine/require' -import type {State as ConfigStore} from '../config' -import {storeRegistry} from '../store-registry' +import {useConfigState, type State as ConfigStore} from '@/stores/config' type Store = T.Immutable<{ devices: ConfigStore['unlockFoldersDevices'] @@ -39,7 +38,7 @@ export const useUnlockFoldersState = Z.createZustand((set, _get) => { case EngineGen.keybase1RekeyUIRefresh: { const {problemSetDevices} = action.payload.params logger.info('Asked for rekey') - storeRegistry.getState('config').dispatch.openUnlockFolders(problemSetDevices.devices ?? []) + useConfigState.getState().dispatch.openUnlockFolders(problemSetDevices.devices ?? []) break } case EngineGen.keybase1RekeyUIDelegateRekeyUI: { @@ -49,7 +48,7 @@ export const useUnlockFoldersState = Z.createZustand((set, _get) => { dangling: true, incomingCallMap: { 'keybase.1.rekeyUI.refresh': ({problemSetDevices}) => { - storeRegistry.getState('config').dispatch.openUnlockFolders(problemSetDevices.devices ?? []) + useConfigState.getState().dispatch.openUnlockFolders(problemSetDevices.devices ?? []) }, 'keybase.1.rekeyUI.rekeySendEvent': () => {}, // ignored debug call from daemon }, diff --git a/shared/constants/users/index.tsx b/shared/stores/users.tsx similarity index 96% rename from shared/constants/users/index.tsx rename to shared/stores/users.tsx index 642c3c0f3ec9..618264f110ac 100644 --- a/shared/constants/users/index.tsx +++ b/shared/stores/users.tsx @@ -1,11 +1,11 @@ import * as EngineGen from '@/actions/engine-gen-gen' import * as Z from '@/util/zustand' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import {mapGetEnsureValue} from '@/util/map' -import {ignorePromise} from '../utils' -import {RPCError, isNetworkErr} from '../utils' -import * as S from '../strings' +import {ignorePromise} from '@/constants/utils' +import {RPCError, isNetworkErr} from '@/constants/utils' +import * as S from '@/constants/strings' type Store = T.Immutable<{ blockMap: Map diff --git a/shared/constants/waiting/index.tsx b/shared/stores/waiting.tsx similarity index 94% rename from shared/constants/waiting/index.tsx rename to shared/stores/waiting.tsx index 4ba0a2f8388a..effcb51b5006 100644 --- a/shared/constants/waiting/index.tsx +++ b/shared/stores/waiting.tsx @@ -1,7 +1,8 @@ import type {RPCError} from '@/util/errors' -import type * as T from '../types' +import type * as T from '@/constants/types' import * as Z from '@/util/zustand' +// This store has no dependencies on other stores and is safe to import directly from other stores. const initialStore: T.Waiting.State = { counts: new Map(), errors: new Map(), diff --git a/shared/constants/wallets/index.tsx b/shared/stores/wallets.tsx similarity index 84% rename from shared/constants/wallets/index.tsx rename to shared/stores/wallets.tsx index b6d0b28eff96..0305e954dfd6 100644 --- a/shared/constants/wallets/index.tsx +++ b/shared/stores/wallets.tsx @@ -1,10 +1,10 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' -import {loadAccountsWaitingKey} from './utils' -import {storeRegistry} from '../store-registry' +import {loadAccountsWaitingKey} from '@/constants/strings' +import {useConfigState} from '@/stores/config' -export {loadAccountsWaitingKey} from './utils' +export {loadAccountsWaitingKey} from '@/constants/strings' export type Account = { accountID: string @@ -33,7 +33,7 @@ export const useState = Z.createZustand((set, get) => { const dispatch: State['dispatch'] = { load: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } const res = await T.RPCStellar.localGetWalletAccountsLocalRpcPromise(undefined, [ diff --git a/shared/stores/whats-new.tsx b/shared/stores/whats-new.tsx new file mode 100644 index 000000000000..c32512b33505 --- /dev/null +++ b/shared/stores/whats-new.tsx @@ -0,0 +1,118 @@ +import type * as T from '@/constants/types' +import * as Z from '@/util/zustand' +import {uint8ArrayToString} from 'uint8array-extras' +import {currentVersion, lastVersion, lastLastVersion} from '@/constants/strings' +export {currentVersion, lastVersion, lastLastVersion, keybaseFM} from '@/constants/strings' + +const noVersion: string = '0.0.0' +export {noVersion} + +// This store has no dependencies on other stores and is safe to import directly from other stores. +type SeenVersionsMap = {[key in string]: boolean} + +const semver = { + gte: (a: string, b: string) => { + const arra = a.split('.').map(i => parseInt(i)) + const [a1, a2, a3] = arra + const arrb = b.split('.').map(i => parseInt(i)) + const [b1, b2, b3] = arrb + if (arra.length === 3 && arrb.length === 3) { + return a1! >= b1! && a2! >= b2! && a3! >= b3! + } else { + return false + } + }, + valid: (v: string) => + v.split('.').reduce((cnt, i) => { + if (parseInt(i) >= 0) { + return cnt + 1 + } + return cnt + }, 0) === 3, +} + +const versions = [currentVersion, lastVersion, lastLastVersion, noVersion] as const + +const isVersionValid = (version: string) => { + return version ? semver.valid(version) : false +} + +const getSeenVersions = (lastSeenVersion: string): SeenVersionsMap => { + const initialMap: SeenVersionsMap = { + [currentVersion]: true, + [lastLastVersion]: true, + [lastVersion]: true, + [noVersion]: true, + } + + if (!lastSeenVersion || !semver.valid(lastSeenVersion)) { + return initialMap + } + if (lastSeenVersion === noVersion) { + return { + [currentVersion]: false, + [lastLastVersion]: false, + [lastVersion]: false, + [noVersion]: false, + } + } + + const validVersions = versions.filter(isVersionValid) + + const seenVersions = validVersions.reduce( + (acc, version) => ({ + ...acc, + [version]: version === noVersion ? true : semver.gte(lastSeenVersion, version), + }), + initialMap + ) + + return seenVersions +} + +type Store = T.Immutable<{ + lastSeenVersion: string + seenVersions: SeenVersionsMap +}> +const initialStore: Store = { + lastSeenVersion: '', + seenVersions: getSeenVersions(''), +} +export interface State extends Store { + dispatch: { + resetState: 'default' + updateLastSeen: (lastSeenItem?: {md: T.RPCGen.Gregor1.Metadata; item: T.RPCGen.Gregor1.Item}) => void + } + anyVersionsUnseen: () => boolean +} +export const useWhatsNewState = Z.createZustand((set, get) => { + const dispatch: State['dispatch'] = { + resetState: 'default', + updateLastSeen: lastSeenItem => { + if (lastSeenItem) { + const {body} = lastSeenItem.item + const pushStateLastSeenVersion = uint8ArrayToString(body) + const lastSeenVersion = pushStateLastSeenVersion || noVersion + // Default to 0.0.0 (noVersion) if user has never marked a version as seen + set(s => { + s.lastSeenVersion = lastSeenVersion + s.seenVersions = getSeenVersions(lastSeenVersion) + }) + } else { + set(s => { + s.lastSeenVersion = noVersion + s.seenVersions = getSeenVersions(noVersion) + }) + } + }, + } + return { + ...initialStore, + anyVersionsUnseen: () => { + const {lastSeenVersion: ver} = get() + // On first load of what's new, lastSeenVersion == noVersion so everything is unseen + return ver !== '' && ver === noVersion ? true : Object.values(getSeenVersions(ver)).some(seen => !seen) + }, + dispatch, + } +}) diff --git a/shared/styles/colors.tsx b/shared/styles/colors.tsx index f84ad80c59b3..76c0da24acae 100644 --- a/shared/styles/colors.tsx +++ b/shared/styles/colors.tsx @@ -1,5 +1,5 @@ // the _on_white are precomputed colors so we can do less blending on mobile -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import {isIOS, isAndroid} from '@/constants/platform' import type {DynamicColorIOS as DynamicColorIOSType} from 'react-native' import type {Opaque} from '@/constants/types/ts' diff --git a/shared/styles/index.native.tsx b/shared/styles/index.native.tsx index 0fc8b52b108c..a15e6e2db217 100644 --- a/shared/styles/index.native.tsx +++ b/shared/styles/index.native.tsx @@ -2,7 +2,7 @@ import * as Shared from './shared' import {colors as lightColors} from './colors' import styleSheetCreateProxy, {type MapToStyles} from './style-sheet-proxy' import {StyleSheet, Dimensions} from 'react-native' -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import {isIOS, isTablet} from '@/constants/platform' const font = isIOS diff --git a/shared/styles/style-sheet-proxy.tsx b/shared/styles/style-sheet-proxy.tsx index 37cb8404f801..a6d33b090b3b 100644 --- a/shared/styles/style-sheet-proxy.tsx +++ b/shared/styles/style-sheet-proxy.tsx @@ -1,4 +1,4 @@ -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import type {StylesCrossPlatform} from '.' // Support a closure to enable simple dark mode. diff --git a/shared/team-building/contacts.tsx b/shared/team-building/contacts.tsx index 6d39827419c6..06edc5fb79f2 100644 --- a/shared/team-building/contacts.tsx +++ b/shared/team-building/contacts.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {useTBContext} from '@/constants/team-building' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useTBContext} from '@/stores/team-building' const useContactsProps = () => { const contactsImported = useSettingsContactsState(s => s.importEnabled) diff --git a/shared/team-building/email-search.tsx b/shared/team-building/email-search.tsx index 669e819c92a4..2f5f8f17930d 100644 --- a/shared/team-building/email-search.tsx +++ b/shared/team-building/email-search.tsx @@ -1,11 +1,12 @@ import * as C from '@/constants' -import * as TB from '@/constants/team-building' +import * as TB from '@/stores/team-building' import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {validateEmailAddress} from '@/util/email-address' import {UserMatchMention} from './phone-search' import ContinueButton from './continue-button' +import {searchWaitingKey} from '@/constants/strings' type EmailSearchProps = { continueLabel: string @@ -17,7 +18,7 @@ const EmailSearch = ({continueLabel, namespace, search}: EmailSearchProps) => { const teamBuildingSearchResults = TB.useTBContext(s => s.searchResults) const [isEmailValid, setEmailValidity] = React.useState(false) const [emailString, setEmailString] = React.useState('') - const waiting = C.Waiting.useAnyWaiting(TB.searchWaitingKey) + const waiting = C.Waiting.useAnyWaiting(searchWaitingKey) const user: T.TB.User | undefined = teamBuildingSearchResults.get(emailString)?.get('email')?.[0] const canSubmit = !!user && !waiting && isEmailValid diff --git a/shared/team-building/filtered-service-tab-bar.tsx b/shared/team-building/filtered-service-tab-bar.tsx index 353ce9fa5665..b882965f5429 100644 --- a/shared/team-building/filtered-service-tab-bar.tsx +++ b/shared/team-building/filtered-service-tab-bar.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import type * as T from '@/constants/types' import {ServiceTabBar} from './service-tab-bar' -import * as TeamBuilding from '@/constants/team-building' +import * as TeamBuilding from '@/stores/team-building' export const FilteredServiceTabBar = ( props: Omit, 'services'> & { diff --git a/shared/team-building/index.tsx b/shared/team-building/index.tsx index bbc6518549f4..7b94ab175862 100644 --- a/shared/team-building/index.tsx +++ b/shared/team-building/index.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as TB from '@/constants/team-building' -import * as Teams from '@/constants/teams' +import * as TB from '@/stores/team-building' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' diff --git a/shared/team-building/list-body.tsx b/shared/team-building/list-body.tsx index d9cb0fcbeb9a..20b0bcd9dac5 100644 --- a/shared/team-building/list-body.tsx +++ b/shared/team-building/list-body.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import * as TB from '@/constants/team-building' -import {useTeamsState} from '@/constants/teams' +import * as TB from '@/stores/team-building' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as Shared from './shared' import PeopleResult from './search-result/people-result' @@ -14,9 +14,9 @@ import type {RootRouteProps} from '@/router-v2/route-params' import {RecsAndRecos, numSectionLabel} from './recs-and-recos' import {formatAnyPhoneNumbers} from '@/util/phone-numbers' import {useRoute} from '@react-navigation/native' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' // import {useAnimatedScrollHandler} from '@/common-adapters/reanimated' import {useColorScheme} from 'react-native' diff --git a/shared/team-building/page.tsx b/shared/team-building/page.tsx index a5f899216400..083c6b96837b 100644 --- a/shared/team-building/page.tsx +++ b/shared/team-building/page.tsx @@ -1,7 +1,7 @@ import type * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' -import {TBProvider} from '@/constants/team-building' +import {TBProvider} from '@/stores/team-building' const getOptions = ({route}: OwnProps) => { const namespace: unknown = route.params.namespace diff --git a/shared/team-building/phone-search.tsx b/shared/team-building/phone-search.tsx index 4a93fd2dd078..7c3a04a330bd 100644 --- a/shared/team-building/phone-search.tsx +++ b/shared/team-building/phone-search.tsx @@ -1,10 +1,11 @@ import * as C from '@/constants' -import * as TB from '@/constants/team-building' +import * as TB from '@/stores/team-building' import * as React from 'react' import * as Kb from '@/common-adapters/index' -import type * as T from 'constants/types' +import type * as T from '@/constants/types' import ContinueButton from './continue-button' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {searchWaitingKey} from '@/constants/strings' type PhoneSearchProps = { continueLabel: string @@ -18,7 +19,7 @@ const PhoneSearch = (props: PhoneSearchProps) => { const [isPhoneValid, setPhoneValidity] = React.useState(false) const [phoneNumber, setPhoneNumber] = React.useState('') const [phoneInputKey, setPhoneInputKey] = React.useState(0) - const waiting = C.Waiting.useAnyWaiting(TB.searchWaitingKey) + const waiting = C.Waiting.useAnyWaiting(searchWaitingKey) const loadDefaultPhoneCountry = useSettingsPhoneState(s => s.dispatch.loadDefaultPhoneCountry) // trigger a default phone number country rpc if it's not already loaded const defaultCountry = useSettingsPhoneState(s => s.defaultCountry) diff --git a/shared/team-building/search-result/hellobot-result.tsx b/shared/team-building/search-result/hellobot-result.tsx index 029e730955d1..8e080121a890 100644 --- a/shared/team-building/search-result/hellobot-result.tsx +++ b/shared/team-building/search-result/hellobot-result.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useTBContext} from '@/constants/team-building' +import {useTBContext} from '@/stores/team-building' import * as Kb from '@/common-adapters' import CommonResult, {type ResultProps} from './common-result' diff --git a/shared/team-building/search-result/people-result.tsx b/shared/team-building/search-result/people-result.tsx index 82bf99580a2c..7925b8767ac3 100644 --- a/shared/team-building/search-result/people-result.tsx +++ b/shared/team-building/search-result/people-result.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import CommonResult, {type ResultProps} from './common-result' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' /* * This component is intended to be a drop-in replacement for UserResult. @@ -32,14 +32,14 @@ const PeopleResult = React.memo(function PeopleResult(props: ResultProps) { const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const onOpenPrivateFolder = React.useCallback(() => { navigateUp() - FS.makeActionForOpenPathInFilesTab( + FS.navToPath( T.FS.stringToPath(`/keybase/private/${decoratedUsername},${myUsername}`) ) }, [navigateUp, decoratedUsername, myUsername]) const onBrowsePublicFolder = React.useCallback(() => { navigateUp() - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/public/${decoratedUsername}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/public/${decoratedUsername}`)) }, [navigateUp, decoratedUsername]) const onManageBlocking = React.useCallback(() => { diff --git a/shared/team-building/search-result/you-result.tsx b/shared/team-building/search-result/you-result.tsx index 0e98651cd20a..0c482c089fc9 100644 --- a/shared/team-building/search-result/you-result.tsx +++ b/shared/team-building/search-result/you-result.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useTBContext} from '@/constants/team-building' +import {useTBContext} from '@/stores/team-building' import * as Kb from '@/common-adapters' import CommonResult, {type ResultProps} from './common-result' diff --git a/shared/team-building/shared.tsx b/shared/team-building/shared.tsx index 6342bd06fd0b..9303fdcff0ef 100644 --- a/shared/team-building/shared.tsx +++ b/shared/team-building/shared.tsx @@ -1,7 +1,7 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {IconType} from '@/common-adapters/icon.constants-gen' -import * as TeamBuilding from '@/constants/team-building' +import * as TeamBuilding from '@/stores/team-building' const services: { [K in T.TB.ServiceIdWithContact]: { diff --git a/shared/teams/add-members-wizard/add-contacts.native.tsx b/shared/teams/add-members-wizard/add-contacts.native.tsx index 6808ecaea271..e5e430e0bea1 100644 --- a/shared/teams/add-members-wizard/add-contacts.native.tsx +++ b/shared/teams/add-members-wizard/add-contacts.native.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {pluralize} from '@/util/string' diff --git a/shared/teams/add-members-wizard/add-email.tsx b/shared/teams/add-members-wizard/add-email.tsx index 26cf7893dc31..c19e84f68547 100644 --- a/shared/teams/add-members-wizard/add-email.tsx +++ b/shared/teams/add-members-wizard/add-email.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' import * as T from '@/constants/types' diff --git a/shared/teams/add-members-wizard/add-from-where.tsx b/shared/teams/add-members-wizard/add-from-where.tsx index 08c3f171a503..8bda843d03cf 100644 --- a/shared/teams/add-members-wizard/add-from-where.tsx +++ b/shared/teams/add-members-wizard/add-from-where.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import {ModalTitle} from '../common' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/add-members-wizard/add-phone.tsx b/shared/teams/add-members-wizard/add-phone.tsx index 9a93ef6f9c4b..5ed2a3a15ae2 100644 --- a/shared/teams/add-members-wizard/add-phone.tsx +++ b/shared/teams/add-members-wizard/add-phone.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {ModalTitle, usePhoneNumberList} from '../common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' const waitingKey = 'phoneLookup' diff --git a/shared/teams/add-members-wizard/confirm.tsx b/shared/teams/add-members-wizard/confirm.tsx index 743ef7110357..6c4fab1bef42 100644 --- a/shared/teams/add-members-wizard/confirm.tsx +++ b/shared/teams/add-members-wizard/confirm.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {assertionToDisplay} from '@/common-adapters/usernames' diff --git a/shared/teams/channel/create-channels.tsx b/shared/teams/channel/create-channels.tsx index 2a11aa71da5b..ab94118ac4c5 100644 --- a/shared/teams/channel/create-channels.tsx +++ b/shared/teams/channel/create-channels.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {CreateChannelsModal} from '../new-team/wizard/create-channels' diff --git a/shared/teams/channel/header.tsx b/shared/teams/channel/header.tsx index ea4654017c5b..16a840f36f2e 100644 --- a/shared/teams/channel/header.tsx +++ b/shared/teams/channel/header.tsx @@ -1,9 +1,9 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {pluralize} from '@/util/string' import {Activity, useChannelParticipants} from '../common' diff --git a/shared/teams/channel/index.tsx b/shared/teams/channel/index.tsx index 0bad5f015636..0af5d5e9274d 100644 --- a/shared/teams/channel/index.tsx +++ b/shared/teams/channel/index.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import { @@ -15,8 +15,8 @@ import ChannelMemberRow from './rows' import BotRow from '../team/rows/bot-row/bot' import SettingsList from '../../chat/conversation/info-panel/settings' import EmptyRow from '../team/rows/empty-row' -import {useBotsState} from '@/constants/bots' -import {useUsersState} from '@/constants/users' +import {useBotsState} from '@/stores/bots' +import {useUsersState} from '@/stores/users' export type OwnProps = { teamID: T.Teams.TeamID diff --git a/shared/teams/channel/rows.tsx b/shared/teams/channel/rows.tsx index e07037b57201..5c77b0f6a518 100644 --- a/shared/teams/channel/rows.tsx +++ b/shared/teams/channel/rows.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' import MenuHeader from '../team/rows/menu-header.new' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' type Props = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/teams/channel/tabs.tsx b/shared/teams/channel/tabs.tsx index 795cfb89ab9e..bc8d5526ea3a 100644 --- a/shared/teams/channel/tabs.tsx +++ b/shared/teams/channel/tabs.tsx @@ -1,7 +1,7 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {Tab as TabType} from '@/common-adapters/tabs' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' export type TabKey = 'members' | 'attachments' | 'bots' | 'settings' | 'loading' diff --git a/shared/teams/common/activity.tsx b/shared/teams/common/activity.tsx index 6ebbf1f4c098..f31a80050dff 100644 --- a/shared/teams/common/activity.tsx +++ b/shared/teams/common/activity.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/teams/common/channel-hooks.tsx b/shared/teams/common/channel-hooks.tsx index 934b7100704b..7736f46a4e49 100644 --- a/shared/teams/common/channel-hooks.tsx +++ b/shared/teams/common/channel-hooks.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' // Filter bots out using team role info, isolate to only when related state changes export const useChannelParticipants = ( diff --git a/shared/teams/common/enable-contacts.tsx b/shared/teams/common/enable-contacts.tsx index 133d5cb95967..477d3faf51dd 100644 --- a/shared/teams/common/enable-contacts.tsx +++ b/shared/teams/common/enable-contacts.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' /** * Popup explaining that Keybase doesn't have contact permissions with a link to @@ -11,7 +11,7 @@ import {useConfigState} from '@/constants/config' * popup. */ const EnableContactsPopup = ({noAccess, onClose}: {noAccess: boolean; onClose: () => void}) => { - const onOpenSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const [showingPopup, setShowingPopup] = React.useState(noAccess) React.useEffect(() => { diff --git a/shared/teams/common/selection-popup.tsx b/shared/teams/common/selection-popup.tsx index 829c6d3200c9..531efe9d707a 100644 --- a/shared/teams/common/selection-popup.tsx +++ b/shared/teams/common/selection-popup.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import {FloatingRolePicker} from '../role-picker' diff --git a/shared/teams/common/use-contacts.native.tsx b/shared/teams/common/use-contacts.native.tsx index 822c30fea955..aa8329ca7130 100644 --- a/shared/teams/common/use-contacts.native.tsx +++ b/shared/teams/common/use-contacts.native.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import {e164ToDisplay} from '@/util/phone-numbers' import logger from '@/logger' import {getDefaultCountryCode} from 'react-native-kb' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {getE164} from '@/constants/settings-phone' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {getE164} from '@/util/phone-numbers' // Contact info coming from the native contacts library. export type Contact = { diff --git a/shared/teams/confirm-modals/confirm-kick-out.tsx b/shared/teams/confirm-modals/confirm-kick-out.tsx index 5a8a0d0624e1..4c9d358d20a3 100644 --- a/shared/teams/confirm-modals/confirm-kick-out.tsx +++ b/shared/teams/confirm-modals/confirm-kick-out.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/confirm-modals/confirm-remove-from-channel.tsx b/shared/teams/confirm-modals/confirm-remove-from-channel.tsx index 4c346846133f..c05aa0d0d84b 100644 --- a/shared/teams/confirm-modals/confirm-remove-from-channel.tsx +++ b/shared/teams/confirm-modals/confirm-remove-from-channel.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/confirm-modals/delete-channel.tsx b/shared/teams/confirm-modals/delete-channel.tsx index 9ad085f55ab3..c2e44e152a52 100644 --- a/shared/teams/confirm-modals/delete-channel.tsx +++ b/shared/teams/confirm-modals/delete-channel.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import {pluralize} from '@/util/string' import {useAllChannelMetas} from '@/teams/common/channel-hooks' diff --git a/shared/teams/confirm-modals/really-leave-team/index.tsx b/shared/teams/confirm-modals/really-leave-team/index.tsx index 684726a1e8a4..9d4340be3b68 100644 --- a/shared/teams/confirm-modals/really-leave-team/index.tsx +++ b/shared/teams/confirm-modals/really-leave-team/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeSubmit} from '@/util/safe-submit' import type * as T from '@/constants/types' diff --git a/shared/teams/container.tsx b/shared/teams/container.tsx index f3219023e020..890288fea27c 100644 --- a/shared/teams/container.tsx +++ b/shared/teams/container.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import Main from './main' import openURL from '@/util/open-url' import {useTeamsSubscribe} from './subscriber' import {useActivityLevels} from './common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const orderTeams = ( teams: ReadonlyMap, @@ -78,7 +78,7 @@ const Connected = () => { updateGregorCategory('sawChatBanner', 'true') } const onOpenFolder = (teamname: T.Teams.Teamname) => { - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/team/${teamname}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/team/${teamname}`)) } const onReadMore = () => { openURL('https://keybase.io/blog/introducing-keybase-teams') diff --git a/shared/teams/delete-team.tsx b/shared/teams/delete-team.tsx index e0dfd2750e0f..4c69295c90ac 100644 --- a/shared/teams/delete-team.tsx +++ b/shared/teams/delete-team.tsx @@ -6,8 +6,8 @@ import * as Kb from '@/common-adapters' import {pluralize} from '@/util/string' import {useTeamDetailsSubscribe} from './subscriber' import noop from 'lodash/noop' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type OwnProps = {teamID: T.Teams.TeamID} diff --git a/shared/teams/edit-team-description.tsx b/shared/teams/edit-team-description.tsx index 7be608185f4c..71e0934031c3 100644 --- a/shared/teams/edit-team-description.tsx +++ b/shared/teams/edit-team-description.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {ModalTitle} from './common' diff --git a/shared/teams/emojis/add-alias.tsx b/shared/teams/emojis/add-alias.tsx index cfbf1a23f600..728b3e93a2a4 100644 --- a/shared/teams/emojis/add-alias.tsx +++ b/shared/teams/emojis/add-alias.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {EmojiPickerDesktop} from '@/chat/emoji-picker/container' diff --git a/shared/teams/emojis/add-emoji.tsx b/shared/teams/emojis/add-emoji.tsx index b1f9a3343e17..81c4e896a1f7 100644 --- a/shared/teams/emojis/add-emoji.tsx +++ b/shared/teams/emojis/add-emoji.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {AliasInput, Modal} from './common' diff --git a/shared/teams/external-team.tsx b/shared/teams/external-team.tsx index 642461031ab6..9fd9da9f4f66 100644 --- a/shared/teams/external-team.tsx +++ b/shared/teams/external-team.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import {useTeamLinkPopup} from './common' import {pluralize} from '@/util/string' import capitalize from 'lodash/capitalize' diff --git a/shared/teams/get-options.tsx b/shared/teams/get-options.tsx index e4aca9f077a2..f0cf90c88b0f 100644 --- a/shared/teams/get-options.tsx +++ b/shared/teams/get-options.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import {HeaderRightActions} from './main/header' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const useHeaderActions = () => { const nav = useSafeNavigation() diff --git a/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx b/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx index c0afab8186de..cffc7f924255 100644 --- a/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx +++ b/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx @@ -1,11 +1,11 @@ import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import useContacts, {type Contact} from '../common/use-contacts.native' import {InviteByContact, type ContactRowProps} from './index.native' import {useTeamDetailsSubscribe} from '../subscriber' import {useSafeNavigation} from '@/util/safe-navigation' -import {getE164} from '@/constants/settings-phone' +import {getE164} from '@/util/phone-numbers' // Seitan invite names (labels) look like this: "[name] ([phone number])". Try // to derive E164 phone number based on seitan invite name and user's region. diff --git a/shared/teams/invite-by-email.tsx b/shared/teams/invite-by-email.tsx index eb81f145ac77..f0201964e4d5 100644 --- a/shared/teams/invite-by-email.tsx +++ b/shared/teams/invite-by-email.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import type * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import {FloatingRolePicker} from './role-picker' diff --git a/shared/teams/join-team/container.tsx b/shared/teams/join-team/container.tsx index abbdbc6f3941..7a1e2e4df322 100644 --- a/shared/teams/join-team/container.tsx +++ b/shared/teams/join-team/container.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import upperFirst from 'lodash/upperFirst' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/teams/join-team/join-from-invite.tsx b/shared/teams/join-team/join-from-invite.tsx index fc4fa2ea5d33..f87440ceac61 100644 --- a/shared/teams/join-team/join-from-invite.tsx +++ b/shared/teams/join-team/join-from-invite.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {Success} from './container' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/main/index.tsx b/shared/teams/main/index.tsx index 71ee7d1b4daa..7ef9fd136b08 100644 --- a/shared/teams/main/index.tsx +++ b/shared/teams/main/index.tsx @@ -4,7 +4,7 @@ import type * as T from '@/constants/types' import Banner from './banner' import TeamsFooter from './footer' import TeamRowNew from './team-row' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' type DeletedTeam = { teamName: string diff --git a/shared/teams/main/team-row.tsx b/shared/teams/main/team-row.tsx index a2550414088d..524f5e399dca 100644 --- a/shared/teams/main/team-row.tsx +++ b/shared/teams/main/team-row.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' @@ -6,8 +6,8 @@ import TeamMenu from '../team/menu-container' import {pluralize} from '@/util/string' import {Activity} from '../common' import {useSafeNavigation} from '@/util/safe-navigation' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type Props = { firstItem: boolean diff --git a/shared/teams/new-team/index.tsx b/shared/teams/new-team/index.tsx index 864bc75f6e6d..13d7883eaa0d 100644 --- a/shared/teams/new-team/index.tsx +++ b/shared/teams/new-team/index.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import openUrl from '@/util/open-url' diff --git a/shared/teams/new-team/wizard/add-subteam-members.tsx b/shared/teams/new-team/wizard/add-subteam-members.tsx index dc439ccc5bb5..6d9484b18811 100644 --- a/shared/teams/new-team/wizard/add-subteam-members.tsx +++ b/shared/teams/new-team/wizard/add-subteam-members.tsx @@ -1,4 +1,4 @@ -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' @@ -6,7 +6,7 @@ import {ModalTitle} from '@/teams/common' import {pluralize} from '@/util/string' import {useTeamDetailsSubscribe} from '@/teams/subscriber' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const AddSubteamMembers = () => { const nav = useSafeNavigation() diff --git a/shared/teams/new-team/wizard/create-channels.tsx b/shared/teams/new-team/wizard/create-channels.tsx index b2c22534ece2..dda466eb062e 100644 --- a/shared/teams/new-team/wizard/create-channels.tsx +++ b/shared/teams/new-team/wizard/create-channels.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {pluralize} from '@/util/string' diff --git a/shared/teams/new-team/wizard/create-subteams.tsx b/shared/teams/new-team/wizard/create-subteams.tsx index e45764eff76c..89a3097a83d6 100644 --- a/shared/teams/new-team/wizard/create-subteams.tsx +++ b/shared/teams/new-team/wizard/create-subteams.tsx @@ -4,7 +4,7 @@ import * as T from '@/constants/types' import {pluralize} from '@/util/string' import {ModalTitle} from '@/teams/common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const cleanSubteamName = (name: string) => name.replace(/[^0-9a-zA-Z_]/, '') diff --git a/shared/teams/new-team/wizard/make-big-team.tsx b/shared/teams/new-team/wizard/make-big-team.tsx index 016a8edc4b71..071a0e4420f8 100644 --- a/shared/teams/new-team/wizard/make-big-team.tsx +++ b/shared/teams/new-team/wizard/make-big-team.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {ModalTitle} from '@/teams/common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const MakeBigTeam = () => { const nav = useSafeNavigation() diff --git a/shared/teams/new-team/wizard/new-team-info.tsx b/shared/teams/new-team/wizard/new-team-info.tsx index cb9661e3b0ee..0bfb4183f529 100644 --- a/shared/teams/new-team/wizard/new-team-info.tsx +++ b/shared/teams/new-team/wizard/new-team-info.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import {ModalTitle} from '@/teams/common' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import {pluralize} from '@/util/string' import {InlineDropdown} from '@/common-adapters/dropdown' import {FloatingRolePicker} from '../../role-picker' diff --git a/shared/teams/new-team/wizard/team-purpose.tsx b/shared/teams/new-team/wizard/team-purpose.tsx index 2d75aad151e8..4fa97617bb83 100644 --- a/shared/teams/new-team/wizard/team-purpose.tsx +++ b/shared/teams/new-team/wizard/team-purpose.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import {ModalTitle} from '@/teams/common' import * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const TeamPurpose = () => { const nav = useSafeNavigation() diff --git a/shared/teams/rename-team.tsx b/shared/teams/rename-team.tsx index 3b080205a545..bdcb775f658d 100644 --- a/shared/teams/rename-team.tsx +++ b/shared/teams/rename-team.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' type OwnProps = {teamname: string} diff --git a/shared/teams/routes.tsx b/shared/teams/routes.tsx index 5692f5f15451..3d57aef29f3a 100644 --- a/shared/teams/routes.tsx +++ b/shared/teams/routes.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import contactRestricted from '../team-building/contact-restricted.page' import teamsTeamBuilder from '../team-building/page' import teamsRootGetOptions from './get-options' diff --git a/shared/teams/subscriber.tsx b/shared/teams/subscriber.tsx index e4dd3276faef..8ea982cb3645 100644 --- a/shared/teams/subscriber.tsx +++ b/shared/teams/subscriber.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import type * as T from '@/constants/types' // NOTE: If you are in a floating box or otherwise outside the navigation diff --git a/shared/teams/team/index.tsx b/shared/teams/team/index.tsx index 0d83779ee857..9c2ad37aa0a9 100644 --- a/shared/teams/team/index.tsx +++ b/shared/teams/team/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' @@ -18,7 +18,7 @@ import { type Section, type Item, } from './rows' -import {useBotsState} from '@/constants/bots' +import {useBotsState} from '@/stores/bots' type Props = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/member/add-to-channels.tsx b/shared/teams/team/member/add-to-channels.tsx index 79922967b9e1..3e3035cb798e 100644 --- a/shared/teams/team/member/add-to-channels.tsx +++ b/shared/teams/team/member/add-to-channels.tsx @@ -1,14 +1,14 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as Common from '@/teams/common' import {pluralize} from '@/util/string' import {useAllChannelMetas} from '@/teams/common/channel-hooks' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/member/edit-channel.tsx b/shared/teams/team/member/edit-channel.tsx index 95679a91a100..1f1a49e5f552 100644 --- a/shared/teams/team/member/edit-channel.tsx +++ b/shared/teams/team/member/edit-channel.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import {ModalTitle} from '@/teams/common' diff --git a/shared/teams/team/member/index.new.tsx b/shared/teams/team/member/index.new.tsx index 3ac20ce63dfa..c5bcd10e5aa9 100644 --- a/shared/teams/team/member/index.new.tsx +++ b/shared/teams/team/member/index.new.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useCurrentUserState} from '@/constants/current-user' -import * as Teams from '@/constants/teams' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useCurrentUserState} from '@/stores/current-user' +import * as Teams from '@/stores/teams' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/teams/team/menu-container.tsx b/shared/teams/team/menu-container.tsx index 4e63c738378f..5ea551600be8 100644 --- a/shared/teams/team/menu-container.tsx +++ b/shared/teams/team/menu-container.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import type * as React from 'react' -import * as FS from '@/constants/fs/util' -import * as Teams from '@/constants/teams' +import * as FS from '@/constants/fs' +import * as Teams from '@/stores/teams' import capitalize from 'lodash/capitalize' import * as T from '@/constants/types' import {pluralize} from '@/util/string' @@ -98,7 +98,7 @@ const Container = (ownProps: OwnProps) => { navigateAppend({props: {teamID}, selected: 'teamReallyLeaveTeam'}) } const onOpenFolder = (teamname: string) => { - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/team/${teamname}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/team/${teamname}`)) } const items: Kb.MenuItems = ['Divider'] diff --git a/shared/teams/team/new-header.tsx b/shared/teams/team/new-header.tsx index bbdf3a4bb0aa..b5f05946e03b 100644 --- a/shared/teams/team/new-header.tsx +++ b/shared/teams/team/new-header.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import TeamMenu from './menu-container' import {pluralize} from '@/util/string' import {Activity, useActivityLevels, useTeamLinkPopup} from '../common' import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' -import {useTeamsState} from '@/constants/teams' +import {useCurrentUserState} from '@/stores/current-user' +import {useTeamsState} from '@/stores/teams' const AddPeopleButton = ({teamID}: {teamID: T.Teams.TeamID}) => { const startAddMembersWizard = useTeamsState(s => s.dispatch.startAddMembersWizard) diff --git a/shared/teams/team/rows/bot-row/bot.tsx b/shared/teams/team/rows/bot-row/bot.tsx index a6fb1ef29367..4724d6429e39 100644 --- a/shared/teams/team/rows/bot-row/bot.tsx +++ b/shared/teams/team/rows/bot-row/bot.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import BotMenu from './bot-menu' -import {useBotsState} from '@/constants/bots' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useBotsState} from '@/stores/bots' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' export type Props = { botAlias: string diff --git a/shared/teams/team/rows/channel-row/channel.tsx b/shared/teams/team/rows/channel-row/channel.tsx index 846b88deb7e1..3b8b31017ac0 100644 --- a/shared/teams/team/rows/channel-row/channel.tsx +++ b/shared/teams/team/rows/channel-row/channel.tsx @@ -4,8 +4,8 @@ import type * as T from '@/constants/types' import {Activity, useChannelParticipants} from '@/teams/common' import {pluralize} from '@/util/string' import {useSafeNavigation} from '@/util/safe-navigation' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type ChannelRowProps = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/teams/team/rows/emoji-row/add.tsx b/shared/teams/team/rows/emoji-row/add.tsx index b4381e54f327..b48b58e984e4 100644 --- a/shared/teams/team/rows/emoji-row/add.tsx +++ b/shared/teams/team/rows/emoji-row/add.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type OwnProps = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/rows/emoji-row/item.tsx b/shared/teams/team/rows/emoji-row/item.tsx index dfc547d4cafc..41c72d14f68b 100644 --- a/shared/teams/team/rows/emoji-row/item.tsx +++ b/shared/teams/team/rows/emoji-row/item.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as dateFns from 'date-fns' @@ -8,7 +8,7 @@ import {RPCToEmojiData} from '@/common-adapters/emoji' import EmojiMenu from './emoji-menu' import {useEmojiState} from '@/teams/emojis/use-emoji' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/teams/team/rows/empty-row.tsx b/shared/teams/team/rows/empty-row.tsx index b30917726587..df3f9f02eaaa 100644 --- a/shared/teams/team/rows/empty-row.tsx +++ b/shared/teams/team/rows/empty-row.tsx @@ -1,10 +1,10 @@ import type * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { type: 'channelsEmpty' | 'channelsFew' | 'members' | 'subteams' diff --git a/shared/teams/team/rows/index.tsx b/shared/teams/team/rows/index.tsx index cef7d9413a38..ddfe2b0808fa 100644 --- a/shared/teams/team/rows/index.tsx +++ b/shared/teams/team/rows/index.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import EmptyRow from './empty-row' @@ -14,7 +14,7 @@ import {RequestRow, InviteRow} from './invite-row' import {SubteamAddRow, SubteamInfoRow, SubteamTeamRow} from './subteam-row' import {getOrderedMemberArray, sortInvites, getOrderedBotsArray} from './helpers' import {useEmojiState} from '../../emojis/use-emoji' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Requests = Omit, 'firstItem' | 'teamID'> diff --git a/shared/teams/team/rows/invite-row/invite.tsx b/shared/teams/team/rows/invite-row/invite.tsx index 78b2c553a7c0..ff3f9958b352 100644 --- a/shared/teams/team/rows/invite-row/invite.tsx +++ b/shared/teams/team/rows/invite-row/invite.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {formatPhoneNumber} from '@/util/phone-numbers' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' export type Props = { isKeybaseUser?: boolean diff --git a/shared/teams/team/rows/invite-row/request.tsx b/shared/teams/team/rows/invite-row/request.tsx index a4342c9d8e08..36be8c8e829c 100644 --- a/shared/teams/team/rows/invite-row/request.tsx +++ b/shared/teams/team/rows/invite-row/request.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' +import {useProfileState} from '@/stores/profile' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {FloatingRolePicker, sendNotificationFooter} from '@/teams/role-picker' diff --git a/shared/teams/team/rows/member-row.tsx b/shared/teams/team/rows/member-row.tsx index 5c74bd5d754e..7fc8920b2a01 100644 --- a/shared/teams/team/rows/member-row.tsx +++ b/shared/teams/team/rows/member-row.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import MenuHeader from './menu-header.new' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' export type Props = { firstItem: boolean diff --git a/shared/teams/team/rows/subteam-row/add.tsx b/shared/teams/team/rows/subteam-row/add.tsx index 6e80257e3113..58102c169fa9 100644 --- a/shared/teams/team/rows/subteam-row/add.tsx +++ b/shared/teams/team/rows/subteam-row/add.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' diff --git a/shared/teams/team/settings-tab/default-channels.tsx b/shared/teams/team/settings-tab/default-channels.tsx index 4bd9dbf45d56..76524135f8bc 100644 --- a/shared/teams/team/settings-tab/default-channels.tsx +++ b/shared/teams/team/settings-tab/default-channels.tsx @@ -4,8 +4,8 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {RPCError} from '@/util/errors' import {ChannelsWidget} from '@/teams/common' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type Props = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/settings-tab/index.tsx b/shared/teams/team/settings-tab/index.tsx index 264e7690abad..5b83a2071295 100644 --- a/shared/teams/team/settings-tab/index.tsx +++ b/shared/teams/team/settings-tab/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {FloatingRolePicker} from '@/teams/role-picker' diff --git a/shared/teams/team/settings-tab/retention/index.tsx b/shared/teams/team/settings-tab/retention/index.tsx index 3fc393dd4026..0c539384b43c 100644 --- a/shared/teams/team/settings-tab/retention/index.tsx +++ b/shared/teams/team/settings-tab/retention/index.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import type {StylesCrossPlatform} from '@/styles' diff --git a/shared/teams/team/tabs.tsx b/shared/teams/team/tabs.tsx index a27c8027232c..a2fcf15ecd24 100644 --- a/shared/teams/team/tabs.tsx +++ b/shared/teams/team/tabs.tsx @@ -1,8 +1,8 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import type {Tab as TabType} from '@/common-adapters/tabs' type TeamTabsProps = { diff --git a/shared/teams/team/team-info.tsx b/shared/teams/team/team-info.tsx index 5c1a170c27e5..4eb6002f45b9 100644 --- a/shared/teams/team/team-info.tsx +++ b/shared/teams/team/team-info.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {ModalTitle} from '../common' diff --git a/shared/todo.txt b/shared/todo.txt index ae8511c890cb..a13900cfbaa4 100644 --- a/shared/todo.txt +++ b/shared/todo.txt @@ -1,8 +1,8 @@ -android: +TOOD: +react-native-screens header doesn't handle dyanmic colors still https://github.com/software-mansion/react-native-screens/issues/3570 -ios: -input paste view removed <<<<<<<<<<<< +ios: ipad: expo-av to expo-video / expo-audio (not ready yet can't get video size...), missing stuff we need like on full screen change diff --git a/shared/tracker2/assertion.tsx b/shared/tracker2/assertion.tsx index 57a0014647c3..1cf078bf5ece 100644 --- a/shared/tracker2/assertion.tsx +++ b/shared/tracker2/assertion.tsx @@ -1,16 +1,16 @@ import * as React from 'react' import * as C from '@/constants' -import {useConfigState} from '@/constants/config' -import {useCurrentUserState} from '@/constants/current-user' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' import type * as T from '@/constants/types' import openUrl from '@/util/open-url' import * as Kb from '@/common-adapters' import {SiteIcon} from '@/profile/generic/shared' import {formatTimeForAssertionPopup} from '@/util/timestamp' import {useColorScheme} from 'react-native' -import * as Tracker from '@/constants/tracker2' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import * as Tracker from '@/stores/tracker2' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' type OwnProps = { isSuggestion?: boolean @@ -424,7 +424,7 @@ const assertionColorToColor = (c: T.Tracker.AssertionColor) => { const StellarValue = (p: {value: string; color: T.Tracker.AssertionColor}) => { const {value, color} = p - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopyAddress = React.useCallback(() => { copyToClipboard(value) }, [copyToClipboard, value]) diff --git a/shared/tracker2/bio.tsx b/shared/tracker2/bio.tsx index 9113b0f41b25..e26d67af9457 100644 --- a/shared/tracker2/bio.tsx +++ b/shared/tracker2/bio.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useTrackerState} from '@/constants/tracker2' -import {useFollowerState} from '@/constants/followers' +import {useTrackerState} from '@/stores/tracker2' +import {useFollowerState} from '@/stores/followers' type OwnProps = { inTracker: boolean diff --git a/shared/tracker2/remote-container.desktop.tsx b/shared/tracker2/remote-container.desktop.tsx index 16c3549ec2ca..4433a19c09b2 100644 --- a/shared/tracker2/remote-container.desktop.tsx +++ b/shared/tracker2/remote-container.desktop.tsx @@ -1,7 +1,7 @@ // Inside tracker we use an embedded Avatar which is connected. import * as React from 'react' import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as R from '@/constants/remote' import * as RemoteGen from '../actions/remote-gen' import type * as T from '@/constants/types' @@ -9,11 +9,11 @@ import Tracker from './index.desktop' import type {DeserializeProps} from './remote-serializer.desktop' import KB2 from '@/util/electron.desktop' import {useAvatarState} from '@/common-adapters/avatar/store' -import {useTrackerState} from '@/constants/tracker2' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' -import {useDarkModeState} from '@/constants/darkmode' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' +import {useDarkModeState} from '@/stores/darkmode' const {closeWindow} = KB2.functions diff --git a/shared/tracker2/remote-proxy.desktop.tsx b/shared/tracker2/remote-proxy.desktop.tsx index f8ca55fb0c87..0cb25baaab0f 100644 --- a/shared/tracker2/remote-proxy.desktop.tsx +++ b/shared/tracker2/remote-proxy.desktop.tsx @@ -1,7 +1,7 @@ // A mirror of the remote tracker windows. import * as C from '@/constants' import {useAvatarState} from '@/common-adapters/avatar/store' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as React from 'react' import useSerializeProps from '../desktop/remote/use-serialize-props.desktop' import useBrowserWindow from '../desktop/remote/use-browser-window.desktop' @@ -9,10 +9,10 @@ import {serialize, type ProxyProps} from './remote-serializer.desktop' import {intersect} from '@/util/set' import {mapFilterByKey} from '@/util/map' import {useColorScheme} from 'react-native' -import {useTrackerState} from '@/constants/tracker2' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' const MAX_TRACKERS = 5 const windowOpts = {hasShadow: false, height: 470, transparent: true, width: 320} diff --git a/shared/unlock-folders/device-list.desktop.tsx b/shared/unlock-folders/device-list.desktop.tsx index 9a2456b13680..ab64aeb86072 100644 --- a/shared/unlock-folders/device-list.desktop.tsx +++ b/shared/unlock-folders/device-list.desktop.tsx @@ -1,5 +1,5 @@ import * as Kb from '@/common-adapters' -import type {State as ConfigStore} from '@/constants/config' +import type {State as ConfigStore} from '@/stores/config' export type Props = { devices: ConfigStore['unlockFoldersDevices'] diff --git a/shared/unlock-folders/index.desktop.tsx b/shared/unlock-folders/index.desktop.tsx index d8cd03f4894a..af45ee616ebd 100644 --- a/shared/unlock-folders/index.desktop.tsx +++ b/shared/unlock-folders/index.desktop.tsx @@ -4,8 +4,8 @@ import DeviceList from './device-list.desktop' import DragHeader from '../desktop/remote/drag-header.desktop' import PaperKeyInput from './paper-key-input.desktop' import Success from './success.desktop' -import type * as Constants from '@/constants/unlock-folders' -import type {State as ConfigStore} from '@/constants/config' +import type * as Constants from '@/stores/unlock-folders' +import type {State as ConfigStore} from '@/stores/config' export type Props = { phase: Constants.State['phase'] diff --git a/shared/unlock-folders/remote-container.desktop.tsx b/shared/unlock-folders/remote-container.desktop.tsx index 15446563ffc6..18db5590d794 100644 --- a/shared/unlock-folders/remote-container.desktop.tsx +++ b/shared/unlock-folders/remote-container.desktop.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as RemoteGen from '../actions/remote-gen' import UnlockFolders from './index.desktop' import type {DeserializeProps} from './remote-serializer.desktop' -import {useUnlockFoldersState as useUFState} from '@/constants/unlock-folders' -import {useDarkModeState} from '@/constants/darkmode' +import {useUnlockFoldersState as useUFState} from '@/stores/unlock-folders' +import {useDarkModeState} from '@/stores/darkmode' const RemoteContainer = (d: DeserializeProps) => { const {darkMode, devices, waiting, paperKeyError: _error} = d diff --git a/shared/unlock-folders/remote-proxy.desktop.tsx b/shared/unlock-folders/remote-proxy.desktop.tsx index 34ae7ba99c1d..ebe89bf3d4b7 100644 --- a/shared/unlock-folders/remote-proxy.desktop.tsx +++ b/shared/unlock-folders/remote-proxy.desktop.tsx @@ -4,7 +4,7 @@ import useBrowserWindow from '../desktop/remote/use-browser-window.desktop' import useSerializeProps from '../desktop/remote/use-serialize-props.desktop' import {serialize, type ProxyProps} from './remote-serializer.desktop' import {useColorScheme} from 'react-native' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const windowOpts = {height: 300, width: 500} diff --git a/shared/unlock-folders/remote-serializer.desktop.tsx b/shared/unlock-folders/remote-serializer.desktop.tsx index 0370a4c3a86c..b46524dc672d 100644 --- a/shared/unlock-folders/remote-serializer.desktop.tsx +++ b/shared/unlock-folders/remote-serializer.desktop.tsx @@ -1,5 +1,5 @@ import * as T from '@/constants/types' -import type * as ConfigConstants from '@/constants/config' +import type * as ConfigConstants from '@/stores/config' import {produce} from 'immer' export type ProxyProps = { diff --git a/shared/util/crop.tsx b/shared/util/crop.tsx index b06f65bb002b..2f3b745a0c57 100644 --- a/shared/util/crop.tsx +++ b/shared/util/crop.tsx @@ -1,4 +1,4 @@ -import type * as T from '../constants/types' +import type * as T from '@/constants/types' export const fixCrop = (c?: T.RPCChat.Keybase1.ImageCropRect) => { return c diff --git a/shared/util/phone-numbers/index.tsx b/shared/util/phone-numbers/index.tsx index 7c419b18d118..587795dbaf09 100644 --- a/shared/util/phone-numbers/index.tsx +++ b/shared/util/phone-numbers/index.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import libphonenumber from 'google-libphonenumber' const PNF = libphonenumber.PhoneNumberFormat -export const PhoneNumberFormat = PNF +const PhoneNumberFormat = PNF -export const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance() -export const ValidationResult = libphonenumber.PhoneNumberUtil.ValidationResult +const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance() +const ValidationResult = libphonenumber.PhoneNumberUtil.ValidationResult const supported = phoneUtil.getSupportedRegions() export type CountryData = { @@ -188,3 +188,17 @@ export const formatPhoneNumberInternational = (rawNumber: string): string | unde return undefined } } + +// Get phone number in e.164, or null if we can't parse it. +export const getE164 = (phoneNumber: string, countryCode?: string) => { + try { + const parsed = countryCode ? phoneUtil.parse(phoneNumber, countryCode) : phoneUtil.parse(phoneNumber) + const reason = phoneUtil.isPossibleNumberWithReason(parsed) + if (reason !== ValidationResult.IS_POSSIBLE) { + return null + } + return phoneUtil.format(parsed, PhoneNumberFormat.E164) + } catch { + return null + } +} diff --git a/shared/constants/platform-specific/index.d.ts b/shared/util/platform-specific/index.d.ts similarity index 86% rename from shared/constants/platform-specific/index.d.ts rename to shared/util/platform-specific/index.d.ts index 7eefb9fc101e..caecba1087ae 100644 --- a/shared/constants/platform-specific/index.d.ts +++ b/shared/util/platform-specific/index.d.ts @@ -1,4 +1,4 @@ -import type * as T from '../types' +import type * as T from '@/constants/types' type NextURI = string @@ -15,5 +15,4 @@ export declare function saveAttachmentToCameraRoll(fileURL: string, mimeType: st export declare function requestLocationPermission(mode: T.RPCChat.UIWatchPositionPerm): Promise export declare function watchPositionForMap(conversationIDKey: T.Chat.ConversationIDKey): Promise<() => void> -export declare function initPlatformListener(): void export declare function requestPermissionsToWrite(): Promise diff --git a/shared/util/platform-specific/index.desktop.tsx b/shared/util/platform-specific/index.desktop.tsx new file mode 100644 index 000000000000..425732a96ab7 --- /dev/null +++ b/shared/util/platform-specific/index.desktop.tsx @@ -0,0 +1,13 @@ +export const requestPermissionsToWrite = async () => { + return Promise.resolve(true) +} + +export function showShareActionSheet() { + throw new Error('Show Share Action - unsupported on this platform') +} +export async function saveAttachmentToCameraRoll() { + return Promise.reject(new Error('Save Attachment to camera roll - unsupported on this platform')) +} + +export const requestLocationPermission = async () => Promise.resolve() +export const watchPositionForMap = async () => Promise.resolve(() => {}) diff --git a/shared/util/platform-specific/index.native.tsx b/shared/util/platform-specific/index.native.tsx new file mode 100644 index 000000000000..8b1f620d5d75 --- /dev/null +++ b/shared/util/platform-specific/index.native.tsx @@ -0,0 +1,111 @@ +import * as T from '@/constants/types' +import * as ExpoLocation from 'expo-location' +import * as MediaLibrary from 'expo-media-library' +import {addNotificationRequest} from 'react-native-kb' +import logger from '@/logger' +import {ActionSheetIOS} from 'react-native' +import {isIOS, isAndroid} from '@/constants/platform.native' +import {androidShare, androidShareText, androidUnlink} from 'react-native-kb' + +export const requestPermissionsToWrite = async () => { + if (isAndroid) { + const p = await MediaLibrary.requestPermissionsAsync(false) + return p.granted ? Promise.resolve() : Promise.reject(new Error('Unable to acquire storage permissions')) + } + return Promise.resolve() +} + +export const requestLocationPermission = async (mode: T.RPCChat.UIWatchPositionPerm) => { + if (isIOS) { + logger.info('[location] Requesting location perms', mode) + switch (mode) { + case T.RPCChat.UIWatchPositionPerm.base: + { + const iosFGPerms = await ExpoLocation.requestForegroundPermissionsAsync() + if (iosFGPerms.ios?.scope === 'none') { + throw new Error('Please allow Keybase to access your location in the phone settings.') + } + } + break + case T.RPCChat.UIWatchPositionPerm.always: { + const iosBGPerms = await ExpoLocation.requestBackgroundPermissionsAsync() + if (iosBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { + throw new Error( + 'Please allow Keybase to access your location even if the app is not running for live location.' + ) + } + break + } + } + } else if (isAndroid) { + const androidBGPerms = await ExpoLocation.requestForegroundPermissionsAsync() + if (androidBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { + throw new Error('Unable to acquire location permissions') + } + } +} + +export async function saveAttachmentToCameraRoll(filePath: string, mimeType: string): Promise { + const fileURL = 'file://' + filePath + const saveType: 'video' | 'photo' = mimeType.startsWith('video') ? 'video' : 'photo' + const logPrefix = '[saveAttachmentToCameraRoll] ' + try { + try { + // see it we can keep going anyways, android perms are needed sometimes and sometimes not w/ 33 + await requestPermissionsToWrite() + } catch {} + logger.info(logPrefix + `Attempting to save as ${saveType}`) + await MediaLibrary.saveToLibraryAsync(fileURL) + logger.info(logPrefix + 'Success') + } catch (e) { + // This can fail if the user backgrounds too quickly, so throw up a local notification + // just in case to get their attention. + addNotificationRequest({ + body: `Failed to save ${saveType} to camera roll`, + id: Math.floor(Math.random() * 2 ** 32).toString(), + }).catch(() => {}) + logger.debug(logPrefix + 'failed to save: ' + e) + throw e + } finally { + try { + await androidUnlink(filePath) + } catch { + logger.warn('failed to unlink') + } + } +} + +export const showShareActionSheet = async (options: { + filePath?: string + message?: string + mimeType: string +}) => { + if (isIOS) { + return new Promise((resolve, reject) => { + ActionSheetIOS.showShareActionSheetWithOptions( + { + message: options.message, + url: options.filePath, + }, + reject, + resolve + ) + }) + } else { + if (!options.filePath && options.message) { + try { + await androidShareText(options.message, options.mimeType) + return {completed: true, method: ''} + } catch (e) { + throw new Error('Failed to share: ' + String(e)) + } + } + + try { + await androidShare(options.filePath ?? '', options.mimeType) + return {completed: true, method: ''} + } catch (e) { + throw new Error('Failed to share: ' + String(e)) + } + } +} diff --git a/shared/constants/platform-specific/input-monitor.desktop.tsx b/shared/util/platform-specific/input-monitor.desktop.tsx similarity index 100% rename from shared/constants/platform-specific/input-monitor.desktop.tsx rename to shared/util/platform-specific/input-monitor.desktop.tsx diff --git a/shared/constants/platform-specific/kbfs-notifications.tsx b/shared/util/platform-specific/kbfs-notifications.tsx similarity index 98% rename from shared/constants/platform-specific/kbfs-notifications.tsx rename to shared/util/platform-specific/kbfs-notifications.tsx index f06fd2b4c629..1ee3f17113f6 100644 --- a/shared/constants/platform-specific/kbfs-notifications.tsx +++ b/shared/util/platform-specific/kbfs-notifications.tsx @@ -1,5 +1,5 @@ import {pathSep} from '@/constants/platform' -import {storeRegistry} from '@/constants/store-registry' +import {useCurrentUserState} from '@/stores/current-user' import capitalize from 'lodash/capitalize' import * as T from '@/constants/types' import {parseFolderNameToUsers} from '@/util/kbfs' @@ -200,7 +200,7 @@ export function kbfsNotification( let title = `KBFS: ${action}` let body = `Chat or files with ${usernames} ${notification.status}` - const user = storeRegistry.getState('current-user').username + const user = useCurrentUserState.getState().username let rateLimitKey: string const isError = notification.statusCode === T.RPCGen.FSStatusCode.error diff --git a/shared/util/zustand.tsx b/shared/util/zustand.tsx index 340308eb6d95..b3d223636d5b 100644 --- a/shared/util/zustand.tsx +++ b/shared/util/zustand.tsx @@ -9,7 +9,13 @@ import {wrapErrors} from '@/util/debug' // needed for tsc export type {WritableDraft} from 'immer' -type HasReset = {dispatch: {resetDeleteMe?: boolean; resetState: 'default' | (() => void)}} +type HasReset = { + dispatch: { + defer?: Record + resetDeleteMe?: boolean + resetState: 'default' | (() => void) + } +} const resetters: ((isDebug?: boolean) => void)[] = [] const resettersAndDelete: ((isDebug?: boolean) => void)[] = [] @@ -39,8 +45,9 @@ export const createZustand = ( let resetFunc: () => void if (reset === 'default') { resetFunc = () => { + const currentDefer = store.getState().dispatch.defer // eslint-disable-next-line - store.setState(initialState as any, true) + store.setState({...initialState, dispatch: {...initialState.dispatch, defer: currentDefer}} as any, true) } } else { resetFunc = reset diff --git a/shared/wallets/index.tsx b/shared/wallets/index.tsx index 796c7dc738ab..9cdbccfe61df 100644 --- a/shared/wallets/index.tsx +++ b/shared/wallets/index.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as Wallets from '@/constants/wallets' -import {useState as useWalletsState} from '@/constants/wallets' +import * as Wallets from '@/stores/wallets' +import {useState as useWalletsState} from '@/stores/wallets' const Row = (p: {account: Wallets.Account}) => { const {account} = p diff --git a/shared/wallets/really-remove-account.tsx b/shared/wallets/really-remove-account.tsx index e72a8cf90bba..aa7b88fb2143 100644 --- a/shared/wallets/really-remove-account.tsx +++ b/shared/wallets/really-remove-account.tsx @@ -3,9 +3,9 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' import WalletPopup from './wallet-popup' -import * as Wallets from '@/constants/wallets' -import {useState as useWalletsState} from '@/constants/wallets' -import {useConfigState} from '@/constants/config' +import * as Wallets from '@/stores/wallets' +import {useState as useWalletsState} from '@/stores/wallets' +import {useConfigState} from '@/stores/config' type OwnProps = {accountID: string} @@ -17,7 +17,7 @@ const ReallyRemoveAccountPopup = (props: OwnProps) => { const attachmentRef = React.useRef(null) const setShowToastFalseLater = Kb.useTimeout(() => setShowToast(false), 2000) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const [sk, setSK] = React.useState('') const loading = !sk diff --git a/shared/wallets/remove-account.tsx b/shared/wallets/remove-account.tsx index 6088a87265c4..4847147c1825 100644 --- a/shared/wallets/remove-account.tsx +++ b/shared/wallets/remove-account.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import WalletPopup from './wallet-popup' -import {useState as useWalletsState} from '@/constants/wallets' +import {useState as useWalletsState} from '@/stores/wallets' type OwnProps = {accountID: string} diff --git a/shared/whats-new/container.tsx b/shared/whats-new/container.tsx index 605376435ff3..5f2fbf69501d 100644 --- a/shared/whats-new/container.tsx +++ b/shared/whats-new/container.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' import openURL from '@/util/open-url' -import {currentVersion} from '@/constants/whats-new' +import {currentVersion} from '@/stores/whats-new' import {Current, Last, LastLast} from './versions' import WhatsNew from '.' -import {useWhatsNewState as useWNState} from '@/constants/whats-new' -import {useConfigState} from '@/constants/config' +import {useWhatsNewState as useWNState} from '@/stores/whats-new' +import {useConfigState} from '@/stores/config' const WhatsNewContainer = () => { const _onNavigateExternal = (url: string) => { diff --git a/shared/whats-new/icon/index.tsx b/shared/whats-new/icon/index.tsx index 05ca4fb3b455..08ee61d17832 100644 --- a/shared/whats-new/icon/index.tsx +++ b/shared/whats-new/icon/index.tsx @@ -1,10 +1,10 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type {IconStyle} from '@/common-adapters/icon' -import {keybaseFM} from '@/constants/whats-new' +import {keybaseFM} from '@/stores/whats-new' import Popup from './popup' import './icon.css' -import {useWhatsNewState as useWNState} from '@/constants/whats-new' +import {useWhatsNewState as useWNState} from '@/stores/whats-new' type OwnProps = { color?: string diff --git a/shared/whats-new/index.tsx b/shared/whats-new/index.tsx index f52f93af4ef6..f15609db9263 100644 --- a/shared/whats-new/index.tsx +++ b/shared/whats-new/index.tsx @@ -1,7 +1,7 @@ import type * as React from 'react' import * as Kb from '@/common-adapters' import type * as C from '@/constants' -import {currentVersion, lastVersion, lastLastVersion} from '@/constants/whats-new' +import {currentVersion, lastVersion, lastLastVersion} from '@/stores/whats-new' import type {VersionProps} from './versions' type Props = { diff --git a/shared/whats-new/versions.tsx b/shared/whats-new/versions.tsx index cd3763ab6b12..ddf79961c28c 100644 --- a/shared/whats-new/versions.tsx +++ b/shared/whats-new/versions.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import {encryptTab} from '@/constants/crypto/util' +import {encryptTab} from '@/constants/crypto' import type * as React from 'react' import * as Kb from '@/common-adapters' -import {keybaseFM} from '@/constants/whats-new' +import {keybaseFM} from '@/stores/whats-new' import NewFeatureRow from './new-feature-row' -import {settingsCryptoTab, settingsDisplayTab} from '@/constants/settings/util' +import {settingsCryptoTab, settingsDisplayTab} from '@/constants/settings' export type VersionProps = { seen: boolean diff --git a/shared/yarn.lock b/shared/yarn.lock index b1bcd0b269be..5667c8ecec8e 100644 --- a/shared/yarn.lock +++ b/shared/yarn.lock @@ -23,12 +23,47 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": +"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== -"@babel/core@7.28.5", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.0", "@babel/core@^7.24.4", "@babel/core@^7.25.2", "@babel/core@^7.26.0": +"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.0", "@babel/core@^7.24.4", "@babel/core@^7.25.2", "@babel/core@^7.26.0": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== @@ -69,6 +104,17 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" +"@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": version "7.27.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" @@ -87,6 +133,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3", "@babel/helper-create-class-features-plugin@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" @@ -100,7 +157,20 @@ "@babel/traverse" "^7.28.5" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": +"@babel/helper-create-class-features-plugin@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz#611ff5482da9ef0db6291bcd24303400bca170fb" + integrity sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.28.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1", "@babel/helper-create-regexp-features-plugin@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== @@ -120,6 +190,17 @@ lodash.debounce "^4.0.8" resolve "^1.22.10" +"@babel/helper-define-polyfill-provider@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz#714dfe33d8bd710f556df59953720f6eeb6c1a14" + integrity sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA== + dependencies: + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + debug "^4.4.3" + lodash.debounce "^4.0.8" + resolve "^1.22.11" + "@babel/helper-globals@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" @@ -141,6 +222,14 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3": version "7.28.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" @@ -150,6 +239,15 @@ "@babel/helper-validator-identifier" "^7.27.1" "@babel/traverse" "^7.28.3" +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + "@babel/helper-optimise-call-expression@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" @@ -162,6 +260,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== +"@babel/helper-plugin-utils@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== + "@babel/helper-remap-async-to-generator@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" @@ -180,6 +283,15 @@ "@babel/helper-optimise-call-expression" "^7.27.1" "@babel/traverse" "^7.27.1" +"@babel/helper-replace-supers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz#94aa9a1d7423a00aead3f204f78834ce7d53fe44" + integrity sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" @@ -220,6 +332,14 @@ "@babel/template" "^7.27.2" "@babel/types" "^7.28.4" +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/highlight@^7.10.4": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.9.tgz#8141ce68fc73757946f983b343f1231f4691acc6" @@ -230,14 +350,14 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/node@7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.28.0.tgz#fe52d05121ca064e6919215ffe54fea480a8746f" - integrity sha512-6u1Mmn3SIMUH8uwTq543L062X3JDgms9HPf06o/pIGdDjeD/zNQ+dfZPQD27sCyvtP0ZOlJtwnl2RIdPe9bHeQ== +"@babel/node@7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.29.0.tgz#278d903d17bf80d361ed6ac9552125eebd5eab5f" + integrity sha512-9UeU8F3rx2lOZXneEW2HTnTYdA8+fXP0kr54tk7d0fPomWNlZ6WJ2H9lunr5dSvr8FNY0CDnop3Km6jZ5NAUsQ== dependencies: - "@babel/register" "^7.27.1" + "@babel/register" "^7.28.6" commander "^6.2.0" - core-js "^3.30.2" + core-js "^3.48.0" node-environment-flags "^1.0.5" regenerator-runtime "^0.14.0" v8flags "^3.1.1" @@ -249,6 +369,13 @@ dependencies: "@babel/types" "^7.28.5" +"@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== + dependencies: + "@babel/types" "^7.29.0" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz#fbde57974707bbfa0376d34d425ff4fa6c732421" @@ -280,13 +407,13 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-optional-chaining" "^7.27.1" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz#373f6e2de0016f73caf8f27004f61d167743742a" - integrity sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz#0e8289cec28baaf05d54fd08d81ae3676065f69f" + integrity sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/traverse" "^7.28.6" "@babel/plugin-proposal-decorators@^7.12.9": version "7.28.0" @@ -373,20 +500,27 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-syntax-import-assertions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" - integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== +"@babel/plugin-syntax-import-assertions@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz#ae9bc1923a6ba527b70104dd2191b0cd872c8507" + integrity sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.27.1": +"@babel/plugin-syntax-import-attributes@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== dependencies: "@babel/helper-plugin-utils" "^7.27.1" +"@babel/plugin-syntax-import-attributes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -486,7 +620,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-async-generator-functions@^7.25.4", "@babel/plugin-transform-async-generator-functions@^7.28.0": +"@babel/plugin-transform-async-generator-functions@^7.25.4": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== @@ -495,7 +629,16 @@ "@babel/helper-remap-async-to-generator" "^7.27.1" "@babel/traverse" "^7.28.0" -"@babel/plugin-transform-async-to-generator@^7.24.7", "@babel/plugin-transform-async-to-generator@^7.27.1": +"@babel/plugin-transform-async-generator-functions@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz#63ed829820298f0bf143d5a4a68fb8c06ffd742f" + integrity sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/traverse" "^7.29.0" + +"@babel/plugin-transform-async-to-generator@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== @@ -504,6 +647,15 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-remap-async-to-generator" "^7.27.1" +"@babel/plugin-transform-async-to-generator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz#bd97b42237b2d1bc90d74bcb486c39be5b4d7e77" + integrity sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/plugin-transform-block-scoped-functions@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz#558a9d6e24cf72802dd3b62a4b51e0d62c0f57f9" @@ -511,14 +663,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-block-scoping@^7.25.0", "@babel/plugin-transform-block-scoping@^7.28.5": +"@babel/plugin-transform-block-scoping@^7.25.0": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz#e0d3af63bd8c80de2e567e690a54e84d85eb16f6" integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-class-properties@7.27.1", "@babel/plugin-transform-class-properties@^7.25.4", "@babel/plugin-transform-class-properties@^7.27.1": +"@babel/plugin-transform-block-scoping@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz#e1ef5633448c24e76346125c2534eeb359699a99" + integrity sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-class-properties@7.27.1", "@babel/plugin-transform-class-properties@^7.25.4": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== @@ -526,7 +685,15 @@ "@babel/helper-create-class-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-class-static-block@^7.27.1", "@babel/plugin-transform-class-static-block@^7.28.3": +"@babel/plugin-transform-class-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz#d274a4478b6e782d9ea987fda09bdb6d28d66b72" + integrity sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-class-static-block@^7.27.1": version "7.28.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz#d1b8e69b54c9993bc558203e1f49bfc979bfd852" integrity sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg== @@ -534,7 +701,15 @@ "@babel/helper-create-class-features-plugin" "^7.28.3" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-classes@7.28.4", "@babel/plugin-transform-classes@^7.25.4", "@babel/plugin-transform-classes@^7.28.4": +"@babel/plugin-transform-class-static-block@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz#1257491e8259c6d125ac4d9a6f39f9d2bf3dba70" + integrity sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-classes@7.28.4", "@babel/plugin-transform-classes@^7.25.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== @@ -546,7 +721,19 @@ "@babel/helper-replace-supers" "^7.27.1" "@babel/traverse" "^7.28.4" -"@babel/plugin-transform-computed-properties@^7.24.7", "@babel/plugin-transform-computed-properties@^7.27.1": +"@babel/plugin-transform-classes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz#8f6fb79ba3703978e701ce2a97e373aae7dda4b7" + integrity sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/traverse" "^7.28.6" + +"@babel/plugin-transform-computed-properties@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== @@ -554,6 +741,14 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/template" "^7.27.1" +"@babel/plugin-transform-computed-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz#936824fc71c26cb5c433485776d79c8e7b0202d2" + integrity sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/plugin-transform-destructuring@^7.24.8", "@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" @@ -562,13 +757,13 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.28.5" -"@babel/plugin-transform-dotall-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d" - integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== +"@babel/plugin-transform-dotall-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz#def31ed84e0fb6e25c71e53c124e7b76a4ab8e61" + integrity sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-duplicate-keys@^7.27.1": version "7.27.1" @@ -577,13 +772,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec" - integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz#8014b8a6cfd0e7b92762724443bf0d2400f26df1" + integrity sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-dynamic-import@^7.27.1": version "7.27.1" @@ -592,20 +787,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-explicit-resource-management@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz#45be6211b778dbf4b9d54c4e8a2b42fa72e09a1a" - integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== +"@babel/plugin-transform-explicit-resource-management@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz#dd6788f982c8b77e86779d1d029591e39d9d8be7" + integrity sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" -"@babel/plugin-transform-exponentiation-operator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz#7cc90a8170e83532676cfa505278e147056e94fe" - integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== +"@babel/plugin-transform-exponentiation-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz#5e477eb7eafaf2ab5537a04aaafcf37e2d7f1091" + integrity sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-export-namespace-from@^7.25.9", "@babel/plugin-transform-export-namespace-from@^7.27.1": version "7.27.1" @@ -639,12 +834,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.27.1" -"@babel/plugin-transform-json-strings@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c" - integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== +"@babel/plugin-transform-json-strings@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz#4c8c15b2dc49e285d110a4cf3dac52fd2dfc3038" + integrity sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-literals@^7.25.2", "@babel/plugin-transform-literals@^7.27.1": version "7.27.1" @@ -653,13 +848,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-logical-assignment-operators@^7.24.7", "@babel/plugin-transform-logical-assignment-operators@^7.28.5": +"@babel/plugin-transform-logical-assignment-operators@^7.24.7": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz#d028fd6db8c081dee4abebc812c2325e24a85b0e" integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== dependencies: "@babel/helper-plugin-utils" "^7.27.1" +"@babel/plugin-transform-logical-assignment-operators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz#53028a3d77e33c50ef30a8fce5ca17065936e605" + integrity sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-member-expression-literals@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz#37b88ba594d852418e99536f5612f795f23aeaf9" @@ -683,15 +885,23 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-systemjs@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" - integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== +"@babel/plugin-transform-modules-commonjs@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz#c0232e0dfe66a734cc4ad0d5e75fc3321b6fdef1" + integrity sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA== dependencies: - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-modules-systemjs@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz#e458a95a17807c415924106a3ff188a3b8dee964" + integrity sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ== + dependencies: + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.29.0" "@babel/plugin-transform-modules-umd@^7.27.1": version "7.27.1" @@ -701,7 +911,7 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7", "@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== @@ -709,6 +919,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" +"@babel/plugin-transform-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz#a26cd51e09c4718588fc4cce1c5d1c0152102d6a" + integrity sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-new-target@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz#259c43939728cad1706ac17351b7e6a7bea1abeb" @@ -716,21 +934,35 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": +"@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-numeric-separator@^7.24.7", "@babel/plugin-transform-numeric-separator@^7.27.1": +"@babel/plugin-transform-nullish-coalescing-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz#9bc62096e90ab7a887f3ca9c469f6adec5679757" + integrity sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-numeric-separator@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-object-rest-spread@^7.24.7", "@babel/plugin-transform-object-rest-spread@^7.28.4": +"@babel/plugin-transform-numeric-separator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz#1310b0292762e7a4a335df5f580c3320ee7d9e9f" + integrity sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-object-rest-spread@^7.24.7": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== @@ -741,6 +973,17 @@ "@babel/plugin-transform-parameters" "^7.27.7" "@babel/traverse" "^7.28.4" +"@babel/plugin-transform-object-rest-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz#fdd4bc2d72480db6ca42aed5c051f148d7b067f7" + integrity sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA== + dependencies: + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/traverse" "^7.28.6" + "@babel/plugin-transform-object-super@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz#1c932cd27bf3874c43a5cac4f43ebf970c9871b5" @@ -749,13 +992,20 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-replace-supers" "^7.27.1" -"@babel/plugin-transform-optional-catch-binding@^7.24.7", "@babel/plugin-transform-optional-catch-binding@^7.27.1": +"@babel/plugin-transform-optional-catch-binding@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== dependencies: "@babel/helper-plugin-utils" "^7.27.1" +"@babel/plugin-transform-optional-catch-binding@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz#75107be14c78385978201a49c86414a150a20b4c" + integrity sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-optional-chaining@7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz#874ce3c4f06b7780592e946026eb76a32830454f" @@ -764,7 +1014,7 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" -"@babel/plugin-transform-optional-chaining@^7.24.8", "@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": +"@babel/plugin-transform-optional-chaining@^7.24.8", "@babel/plugin-transform-optional-chaining@^7.27.1": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz#8238c785f9d5c1c515a90bf196efb50d075a4b26" integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== @@ -772,6 +1022,14 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" +"@babel/plugin-transform-optional-chaining@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz#926cf150bd421fc8362753e911b4a1b1ce4356cd" + integrity sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-transform-parameters@^7.24.7", "@babel/plugin-transform-parameters@^7.27.7": version "7.27.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz#1fd2febb7c74e7d21cf3b05f7aebc907940af53a" @@ -779,7 +1037,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-methods@^7.24.7", "@babel/plugin-transform-private-methods@^7.27.1": +"@babel/plugin-transform-private-methods@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== @@ -787,7 +1045,15 @@ "@babel/helper-create-class-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-property-in-object@^7.24.7", "@babel/plugin-transform-private-property-in-object@^7.27.1": +"@babel/plugin-transform-private-methods@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz#c76fbfef3b86c775db7f7c106fff544610bdb411" + integrity sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-private-property-in-object@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== @@ -796,6 +1062,15 @@ "@babel/helper-create-class-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" +"@babel/plugin-transform-private-property-in-object@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz#4fafef1e13129d79f1d75ac180c52aafefdb2811" + integrity sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-property-literals@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz#07eafd618800591e88073a0af1b940d9a42c6424" @@ -850,20 +1125,27 @@ "@babel/helper-annotate-as-pure" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regenerator@^7.24.7", "@babel/plugin-transform-regenerator@^7.28.4": +"@babel/plugin-transform-regenerator@^7.24.7": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regexp-modifiers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09" - integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== +"@babel/plugin-transform-regenerator@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz#dec237cec1b93330876d6da9992c4abd42c9d18b" + integrity sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-regexp-modifiers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz#7ef0163bd8b4a610481b2509c58cf217f065290b" + integrity sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-reserved-words@^7.27.1": version "7.27.1" @@ -891,7 +1173,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-spread@^7.24.7", "@babel/plugin-transform-spread@^7.27.1": +"@babel/plugin-transform-spread@^7.24.7": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== @@ -899,6 +1181,14 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" +"@babel/plugin-transform-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz#40a2b423f6db7b70f043ad027a58bcb44a9757b6" + integrity sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-transform-sticky-regex@^7.24.7", "@babel/plugin-transform-sticky-regex@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280" @@ -938,13 +1228,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-property-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956" - integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== +"@babel/plugin-transform-unicode-property-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz#63a7a6c21a0e75dae9b1861454111ea5caa22821" + integrity sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-unicode-regex@7.27.1", "@babel/plugin-transform-unicode-regex@^7.24.7", "@babel/plugin-transform-unicode-regex@^7.27.1": version "7.27.1" @@ -954,88 +1244,88 @@ "@babel/helper-create-regexp-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-sets-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1" - integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== +"@babel/plugin-transform-unicode-sets-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz#924912914e5df9fe615ec472f88ff4788ce04d4e" + integrity sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/preset-env@7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.5.tgz#82dd159d1563f219a1ce94324b3071eb89e280b0" - integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== +"@babel/preset-env@7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.29.0.tgz#c55db400c515a303662faaefd2d87e796efa08d0" + integrity sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w== dependencies: - "@babel/compat-data" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/compat-data" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.6" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.27.1" - "@babel/plugin-syntax-import-attributes" "^7.27.1" + "@babel/plugin-syntax-import-assertions" "^7.28.6" + "@babel/plugin-syntax-import-attributes" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.27.1" - "@babel/plugin-transform-async-generator-functions" "^7.28.0" - "@babel/plugin-transform-async-to-generator" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.29.0" + "@babel/plugin-transform-async-to-generator" "^7.28.6" "@babel/plugin-transform-block-scoped-functions" "^7.27.1" - "@babel/plugin-transform-block-scoping" "^7.28.5" - "@babel/plugin-transform-class-properties" "^7.27.1" - "@babel/plugin-transform-class-static-block" "^7.28.3" - "@babel/plugin-transform-classes" "^7.28.4" - "@babel/plugin-transform-computed-properties" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.28.6" + "@babel/plugin-transform-class-properties" "^7.28.6" + "@babel/plugin-transform-class-static-block" "^7.28.6" + "@babel/plugin-transform-classes" "^7.28.6" + "@babel/plugin-transform-computed-properties" "^7.28.6" "@babel/plugin-transform-destructuring" "^7.28.5" - "@babel/plugin-transform-dotall-regex" "^7.27.1" + "@babel/plugin-transform-dotall-regex" "^7.28.6" "@babel/plugin-transform-duplicate-keys" "^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-dynamic-import" "^7.27.1" - "@babel/plugin-transform-explicit-resource-management" "^7.28.0" - "@babel/plugin-transform-exponentiation-operator" "^7.28.5" + "@babel/plugin-transform-explicit-resource-management" "^7.28.6" + "@babel/plugin-transform-exponentiation-operator" "^7.28.6" "@babel/plugin-transform-export-namespace-from" "^7.27.1" "@babel/plugin-transform-for-of" "^7.27.1" "@babel/plugin-transform-function-name" "^7.27.1" - "@babel/plugin-transform-json-strings" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.28.6" "@babel/plugin-transform-literals" "^7.27.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.6" "@babel/plugin-transform-member-expression-literals" "^7.27.1" "@babel/plugin-transform-modules-amd" "^7.27.1" - "@babel/plugin-transform-modules-commonjs" "^7.27.1" - "@babel/plugin-transform-modules-systemjs" "^7.28.5" + "@babel/plugin-transform-modules-commonjs" "^7.28.6" + "@babel/plugin-transform-modules-systemjs" "^7.29.0" "@babel/plugin-transform-modules-umd" "^7.27.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-new-target" "^7.27.1" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" - "@babel/plugin-transform-numeric-separator" "^7.27.1" - "@babel/plugin-transform-object-rest-spread" "^7.28.4" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6" + "@babel/plugin-transform-numeric-separator" "^7.28.6" + "@babel/plugin-transform-object-rest-spread" "^7.28.6" "@babel/plugin-transform-object-super" "^7.27.1" - "@babel/plugin-transform-optional-catch-binding" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.28.5" + "@babel/plugin-transform-optional-catch-binding" "^7.28.6" + "@babel/plugin-transform-optional-chaining" "^7.28.6" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/plugin-transform-private-methods" "^7.27.1" - "@babel/plugin-transform-private-property-in-object" "^7.27.1" + "@babel/plugin-transform-private-methods" "^7.28.6" + "@babel/plugin-transform-private-property-in-object" "^7.28.6" "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.4" - "@babel/plugin-transform-regexp-modifiers" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.29.0" + "@babel/plugin-transform-regexp-modifiers" "^7.28.6" "@babel/plugin-transform-reserved-words" "^7.27.1" "@babel/plugin-transform-shorthand-properties" "^7.27.1" - "@babel/plugin-transform-spread" "^7.27.1" + "@babel/plugin-transform-spread" "^7.28.6" "@babel/plugin-transform-sticky-regex" "^7.27.1" "@babel/plugin-transform-template-literals" "^7.27.1" "@babel/plugin-transform-typeof-symbol" "^7.27.1" "@babel/plugin-transform-unicode-escapes" "^7.27.1" - "@babel/plugin-transform-unicode-property-regex" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.28.6" "@babel/plugin-transform-unicode-regex" "^7.27.1" - "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.28.6" "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.14" - babel-plugin-polyfill-corejs3 "^0.13.0" - babel-plugin-polyfill-regenerator "^0.6.5" - core-js-compat "^3.43.0" + babel-plugin-polyfill-corejs2 "^0.4.15" + babel-plugin-polyfill-corejs3 "^0.14.0" + babel-plugin-polyfill-regenerator "^0.6.6" + core-js-compat "^3.48.0" semver "^6.3.1" "@babel/preset-modules@0.1.6-no-external-plugins": @@ -1081,10 +1371,10 @@ "@babel/plugin-transform-modules-commonjs" "^7.27.1" "@babel/plugin-transform-typescript" "^7.28.5" -"@babel/register@^7.27.1": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.28.3.tgz#abd8a3753480c799bdaf9c9092d6745d16e052c2" - integrity sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA== +"@babel/register@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.28.6.tgz#f54461dd32f6a418c1eb1f583c95ed0b7266ea4c" + integrity sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1106,6 +1396,15 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" @@ -1132,6 +1431,19 @@ "@babel/types" "^7.28.5" debug "^4.3.1" +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.26.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" @@ -1140,6 +1452,14 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" +"@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@clack/core@0.3.5", "@clack/core@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@clack/core/-/core-0.3.5.tgz#3e1454c83a329353cc3a6ff8491e4284d49565bb" @@ -1424,71 +1744,51 @@ dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.2": version "4.12.2" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== -"@eslint/config-array@^0.21.1": - version "0.21.1" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" - integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== +"@eslint/config-array@^0.23.0": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.1.tgz#908223da7b9148f1af5bfb3144b77a9387a89446" + integrity sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA== dependencies: - "@eslint/object-schema" "^2.1.7" + "@eslint/object-schema" "^3.0.1" debug "^4.3.1" - minimatch "^3.1.2" + minimatch "^10.1.1" -"@eslint/config-helpers@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" - integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== +"@eslint/config-helpers@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.2.tgz#314c7b03d02a371ad8c0a7f6821d5a8a8437ba9d" + integrity sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ== dependencies: - "@eslint/core" "^0.17.0" + "@eslint/core" "^1.1.0" -"@eslint/core@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" - integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== +"@eslint/core@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.0.tgz#51f5cd970e216fbdae6721ac84491f57f965836d" + integrity sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" - integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@9.39.2": - version "9.39.2" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599" - integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== - -"@eslint/object-schema@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" - integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== +"@eslint/object-schema@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.1.tgz#9a1dc9af00d790dc79a9bf57a756e3cb2740ddb9" + integrity sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg== -"@eslint/plugin-kit@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" - integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== +"@eslint/plugin-kit@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz#e0cb12ec66719cb2211ad36499fb516f2a63899d" + integrity sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ== dependencies: - "@eslint/core" "^0.17.0" + "@eslint/core" "^1.1.0" levn "^0.4.1" -"@expo/cli@54.0.21": - version "54.0.21" - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-54.0.21.tgz#196ea08f539535b26717e09661937b24b38f25e7" - integrity sha512-L/FdpyZDsg/Nq6xW6kfiyF9DUzKfLZCKFXEVZcDqCNar6bXxQVotQyvgexRvtUF5nLinuT/UafLOdC3FUALUmA== +"@expo/cli@54.0.23": + version "54.0.23" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-54.0.23.tgz#e8a7dc4e1f2a8a5361afd80bcc352014b57a87ac" + integrity sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g== dependencies: "@0no-co/graphql.web" "^1.0.8" "@expo/code-signing-certificates" "^0.0.6" @@ -1499,9 +1799,9 @@ "@expo/image-utils" "^0.8.8" "@expo/json-file" "^10.0.8" "@expo/metro" "~54.2.0" - "@expo/metro-config" "~54.0.13" + "@expo/metro-config" "~54.0.14" "@expo/osascript" "^2.3.8" - "@expo/package-manager" "^1.9.9" + "@expo/package-manager" "^1.9.10" "@expo/plist" "^0.4.8" "@expo/prebuild-config" "^54.0.8" "@expo/schema-utils" "^0.1.8" @@ -1724,10 +2024,10 @@ "@babel/code-frame" "~7.10.4" json5 "^2.2.3" -"@expo/metro-config@54.0.13", "@expo/metro-config@~54.0.13": - version "54.0.13" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-54.0.13.tgz#a78f0da6ba7eca4d3b1fa0fa2487be4d947e4fd6" - integrity sha512-RRufMCgLR2Za1WGsh02OatIJo5qZFt31yCnIOSfoubNc3Qqe92Z41pVsbrFnmw5CIaisv1NgdBy05DHe7pEyuw== +"@expo/metro-config@54.0.14", "@expo/metro-config@~54.0.14": + version "54.0.14" + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-54.0.14.tgz#e455dfb2bae9473ec665bc830d651baa709c1e8a" + integrity sha512-hxpLyDfOR4L23tJ9W1IbJJsG7k4lv2sotohBm/kTYyiG+pe1SYCAWsRmgk+H42o/wWf/HQjE5k45S5TomGLxNA== dependencies: "@babel/code-frame" "^7.20.0" "@babel/core" "^7.20.0" @@ -1779,10 +2079,10 @@ "@expo/spawn-async" "^1.7.2" exec-async "^2.2.0" -"@expo/package-manager@^1.9.9": - version "1.9.9" - resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.9.9.tgz#dd030d2bccebd095e02bfb6976852afaddcd122a" - integrity sha512-Nv5THOwXzPprMJwbnXU01iXSrCp3vJqly9M4EJ2GkKko9Ifer2ucpg7x6OUsE09/lw+npaoUnHMXwkw7gcKxlg== +"@expo/package-manager@^1.9.10": + version "1.9.10" + resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.9.10.tgz#5da3f4943f6fa44ddd7f0efe32a360040edd734a" + integrity sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA== dependencies: "@expo/json-file" "^10.0.8" "@expo/spawn-async" "^1.7.2" @@ -2118,10 +2418,10 @@ resolved "https://registry.yarnpkg.com/@khanacademy/perseus-utils/-/perseus-utils-2.1.4.tgz#f87462f16781c7a40311ac0932e13a1f67ab9418" integrity sha512-TQLLUZQWc0K0gCLejNTd7G2y1qMhF3BtM9rBGvSObo10EVL4LwA2nCgm0FaPLowCp0ujweZACh9ty+xlekkGXA== -"@khanacademy/simple-markdown@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@khanacademy/simple-markdown/-/simple-markdown-2.1.4.tgz#1673e79fd1d4a2f4c3f697c5421b0a1289ed6f0e" - integrity sha512-2WBPGtnaCuMS2b6DewdXPbgy14QFo6V22FuMf2PxREQaAHakIOhjQ7sgeswN7ZdAZ1uyGi4TnDbN1qSgIbaC3Q== +"@khanacademy/simple-markdown@2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@khanacademy/simple-markdown/-/simple-markdown-2.2.1.tgz#3a477c2bcb026fbf25c0aa0272fbb9b3b0f7058c" + integrity sha512-9xmLOLIh//ZiWwQFtJETnV8Ha24J3KgHpcwrROjqjuXD3tNiNZrVMBBZU93Aw1/jeocA7p5jiYBrErZiqGlLag== dependencies: "@khanacademy/perseus-utils" "2.1.4" @@ -2142,10 +2442,10 @@ resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-2.8.0.tgz#4210deb771ee3912964f14a15ddfb5ff877e70b9" integrity sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ== -"@msgpack/msgpack@3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.1.2.tgz#fdd25cc2202297519798bbaf4689152ad9609e19" - integrity sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ== +"@msgpack/msgpack@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.1.3.tgz#c4bff2b9539faf0882f3ee03537a7e9a4b3a7864" + integrity sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA== "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" @@ -2760,19 +3060,19 @@ invariant "^2.2.4" nullthrows "^1.1.1" -"@react-navigation/bottom-tabs@7.9.1": - version "7.9.1" - resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.9.1.tgz#1a228e68c85f27bb3f0d2b814bdddb25eeb60360" - integrity sha512-1MHn1b5tWFa8t8LXUvaNtlg5WGVXcNTsiV00ygQDBFDusMcu0ZPOU1exqELZwHf6kDntmTQE96/NRM+Cd2QR+A== +"@react-navigation/bottom-tabs@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.13.0.tgz#cef50768633cdbd272809aec7751ac37ce36dab0" + integrity sha512-qxxjRDpjhZ4vIZqG4rBU1Vx2jgOAO/ciUKc9sJqVlTM005E2E+aK1EaE3lGaBDyZxTpjonollAucZcqL7OTscQ== dependencies: - "@react-navigation/elements" "^2.9.4" + "@react-navigation/elements" "^2.9.5" color "^4.2.3" sf-symbols-typescript "^2.1.0" -"@react-navigation/core@7.13.7", "@react-navigation/core@^7.13.7": - version "7.13.7" - resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.13.7.tgz#1263903a703b5f183a08c22dfc5c3735ac4ef91c" - integrity sha512-k2ABo3250vq1ovOh/iVwXS6Hwr5PVRGXoPh/ewVFOOuEKTvOx9i//OBzt8EF+HokBxS2HBRlR2b+aCOmscRqBw== +"@react-navigation/core@7.14.0", "@react-navigation/core@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.14.0.tgz#d24f93d424ab33f645262dc4775e4708aa3d9a8b" + integrity sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g== dependencies: "@react-navigation/routers" "^7.5.3" escape-string-regexp "^4.0.0" @@ -2783,31 +3083,31 @@ use-latest-callback "^0.2.4" use-sync-external-store "^1.5.0" -"@react-navigation/elements@^2.9.4": - version "2.9.4" - resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.4.tgz#72101e7cf74e6952a303ffd3a69aab5e9ba178a8" - integrity sha512-TMFh+QzwesEuSaKpvZk4BFC5105t5gJs9eq+jG7jtfdlMKyrcFwheu2/dy1zfrW4WYbVcoMxhzFqCU4mKwI4fw== +"@react-navigation/elements@^2.9.5": + version "2.9.5" + resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.5.tgz#29f68c4975351724dcfe1d3bdc76c4d6dc65fc33" + integrity sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g== dependencies: color "^4.2.3" use-latest-callback "^0.2.4" use-sync-external-store "^1.5.0" -"@react-navigation/native-stack@7.9.1": - version "7.9.1" - resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.9.1.tgz#0d8f576a9c2ca34344b114ae1e380ad0933dc7b1" - integrity sha512-DIRv72UliHvCWkBO1xwvik0maRka4aebn10huL9u4lPRXZZjumlMFHhA9z8vOaj02ri54m8pQUybRdbVzNeqgQ== +"@react-navigation/native-stack@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.12.0.tgz#0511234ac6030ed6d716561a380cb2225541373c" + integrity sha512-XmNJsPshjkNsahgbxNgGWQUq4s1l6HqH/Fei4QsjBNn/0mTvVrRVZwJ1XrY9YhWYvyiYkAN6/OmarWQaQJ0otQ== dependencies: - "@react-navigation/elements" "^2.9.4" + "@react-navigation/elements" "^2.9.5" color "^4.2.3" sf-symbols-typescript "^2.1.0" warn-once "^0.1.1" -"@react-navigation/native@7.1.27": - version "7.1.27" - resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.1.27.tgz#5be53e9fac9f6173d229b334fe41c2208684bac2" - integrity sha512-kW7LGP/RrisktpGyizTKw6HwSeQJdXnAN9L8GyQJcJAlgL9YtfEg6yEyD5n9RWH90CL8G0cRyUhphKIAFf4lVw== +"@react-navigation/native@7.1.28": + version "7.1.28" + resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.1.28.tgz#1ee75cf3a8b3e4365f94c5d657bb3c015e387720" + integrity sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ== dependencies: - "@react-navigation/core" "^7.13.7" + "@react-navigation/core" "^7.14.0" escape-string-regexp "^4.0.0" fast-deep-equal "^3.1.3" nanoid "^3.3.11" @@ -2966,6 +3266,11 @@ "@types/estree" "*" "@types/json-schema" "*" +"@types/esrecurse@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec" + integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + "@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@^1.0.8": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" @@ -3116,12 +3421,12 @@ dependencies: undici-types "~6.21.0" -"@types/node@^22.7.7": - version "22.19.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.1.tgz#1188f1ddc9f46b4cc3aec76749050b4e1f459b7b" - integrity sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ== +"@types/node@^24.9.0": + version "24.10.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.13.tgz#2fac25c0e30f3848e19912c3b8791a28370e9e07" + integrity sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg== dependencies: - undici-types "~6.21.0" + undici-types "~7.16.0" "@types/qs@*": version "6.14.0" @@ -3164,10 +3469,10 @@ resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.9.tgz#d24b4864c384e770c83275b3fe73fba00269c83b" integrity sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg== -"@types/react@*", "@types/react@19.2.8": - version "19.2.8" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.8.tgz#307011c9f5973a6abab8e17d0293f48843627994" - integrity sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg== +"@types/react@*", "@types/react@19.2.14": + version "19.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" + integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== dependencies: csstype "^3.2.2" @@ -3267,16 +3572,16 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz#afb966c66a2fdc6158cf81118204a971a36d0fc5" - integrity sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg== +"@typescript-eslint/eslint-plugin@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz#086d2ef661507b561f7b17f62d3179d692a0765f" + integrity sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ== dependencies: "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.53.0" - "@typescript-eslint/type-utils" "8.53.0" - "@typescript-eslint/utils" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/scope-manager" "8.55.0" + "@typescript-eslint/type-utils" "8.55.0" + "@typescript-eslint/utils" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" ignore "^7.0.5" natural-compare "^1.4.0" ts-api-utils "^2.4.0" @@ -3296,15 +3601,15 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.53.0.tgz#d8bed6f12dc74e03751e5f947510ff2b165990c6" - integrity sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg== +"@typescript-eslint/parser@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.55.0.tgz#6eace4e9e95f178d3447ed1f17f3d6a5dfdb345c" + integrity sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw== dependencies: - "@typescript-eslint/scope-manager" "8.53.0" - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/scope-manager" "8.55.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" debug "^4.4.3" "@typescript-eslint/parser@^7.1.1": @@ -3318,13 +3623,13 @@ "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" -"@typescript-eslint/project-service@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.53.0.tgz#327c67c61c16a1c8b12a440b0779b41eb77cc7df" - integrity sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg== +"@typescript-eslint/project-service@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.55.0.tgz#b8a71c06a625bdad481c24d5614b68e252f3ae9b" + integrity sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.53.0" - "@typescript-eslint/types" "^8.53.0" + "@typescript-eslint/tsconfig-utils" "^8.55.0" + "@typescript-eslint/types" "^8.55.0" debug "^4.4.3" "@typescript-eslint/scope-manager@5.62.0": @@ -3343,18 +3648,18 @@ "@typescript-eslint/types" "7.18.0" "@typescript-eslint/visitor-keys" "7.18.0" -"@typescript-eslint/scope-manager@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz#f922fcbf0d42e72f065297af31779ccf19de9a97" - integrity sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g== +"@typescript-eslint/scope-manager@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz#8a0752c31c788651840dc98f840b0c2ebe143b8c" + integrity sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q== dependencies: - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" -"@typescript-eslint/tsconfig-utils@8.53.0", "@typescript-eslint/tsconfig-utils@^8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz#105279d7969a7abdc8345cc9c57cff83cf910f8f" - integrity sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA== +"@typescript-eslint/tsconfig-utils@8.55.0", "@typescript-eslint/tsconfig-utils@^8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz#62f1d005419985e09d37a040b2f1450e4e805afa" + integrity sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q== "@typescript-eslint/type-utils@7.18.0": version "7.18.0" @@ -3366,14 +3671,14 @@ debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/type-utils@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz#81a0de5c01fc68f6df0591d03cd8226bda01c91f" - integrity sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw== +"@typescript-eslint/type-utils@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz#195d854b3e56308ce475fdea2165313bb1190200" + integrity sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g== dependencies: - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" - "@typescript-eslint/utils" "8.53.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" + "@typescript-eslint/utils" "8.55.0" debug "^4.4.3" ts-api-utils "^2.4.0" @@ -3387,10 +3692,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== -"@typescript-eslint/types@8.53.0", "@typescript-eslint/types@^8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.53.0.tgz#1adcad3fa32bc2c4cbf3785ba07a5e3151819efb" - integrity sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ== +"@typescript-eslint/types@8.55.0", "@typescript-eslint/types@^8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.55.0.tgz#8449c5a7adac61184cac92dbf6315733569708c2" + integrity sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -3419,15 +3724,15 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/typescript-estree@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz#7805b46b7a8ce97e91b7bb56fc8b1ba26ca8ef52" - integrity sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw== +"@typescript-eslint/typescript-estree@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz#c83ac92c11ce79bedd984937c7780a65e7f7b2e3" + integrity sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw== dependencies: - "@typescript-eslint/project-service" "8.53.0" - "@typescript-eslint/tsconfig-utils" "8.53.0" - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/project-service" "8.55.0" + "@typescript-eslint/tsconfig-utils" "8.55.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" debug "^4.4.3" minimatch "^9.0.5" semver "^7.7.3" @@ -3444,15 +3749,15 @@ "@typescript-eslint/types" "7.18.0" "@typescript-eslint/typescript-estree" "7.18.0" -"@typescript-eslint/utils@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.53.0.tgz#bf0a4e2edaf1afc9abce209fc02f8cab0b74af13" - integrity sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA== +"@typescript-eslint/utils@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.55.0.tgz#c1744d94a3901deb01f58b09d3478d811f96d619" + integrity sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow== dependencies: "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.53.0" - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" + "@typescript-eslint/scope-manager" "8.55.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" "@typescript-eslint/utils@^5.10.0": version "5.62.0" @@ -3484,12 +3789,12 @@ "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" -"@typescript-eslint/visitor-keys@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz#9a785664ddae7e3f7e570ad8166e48dbc9c6cf02" - integrity sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw== +"@typescript-eslint/visitor-keys@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz#3d9a40fd4e3705c63d8fae3af58988add3ed464d" + integrity sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA== dependencies: - "@typescript-eslint/types" "8.53.0" + "@typescript-eslint/types" "8.55.0" eslint-visitor-keys "^4.2.1" "@ungap/structured-clone@^1.3.0": @@ -4059,6 +4364,15 @@ babel-plugin-polyfill-corejs2@^0.4.14: "@babel/helper-define-polyfill-provider" "^0.6.5" semver "^6.3.1" +babel-plugin-polyfill-corejs2@^0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz#808fa349686eea4741807cfaaa2aa3aa57ce120a" + integrity sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-define-polyfill-provider" "^0.6.6" + semver "^6.3.1" + babel-plugin-polyfill-corejs3@^0.13.0: version "0.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164" @@ -4067,6 +4381,14 @@ babel-plugin-polyfill-corejs3@^0.13.0: "@babel/helper-define-polyfill-provider" "^0.6.5" core-js-compat "^3.43.0" +babel-plugin-polyfill-corejs3@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz#65b06cda48d6e447e1e926681f5a247c6ae2b9cf" + integrity sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.6" + core-js-compat "^3.48.0" + babel-plugin-polyfill-regenerator@^0.6.5: version "0.6.5" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" @@ -4074,6 +4396,13 @@ babel-plugin-polyfill-regenerator@^0.6.5: dependencies: "@babel/helper-define-polyfill-provider" "^0.6.5" +babel-plugin-polyfill-regenerator@^0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz#69f5dd263cab933c42fe5ea05e83443b374bd4bf" + integrity sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.6" + babel-plugin-react-compiler@1.0.0, babel-plugin-react-compiler@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz#bdf7360a23a4d5ebfca090255da3893efd07425f" @@ -4121,10 +4450,10 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" -babel-preset-expo@54.0.9, babel-preset-expo@~54.0.9: - version "54.0.9" - resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-54.0.9.tgz#88af355f08dc49b4b54ac559c02ce8890ab08930" - integrity sha512-8J6hRdgEC2eJobjoft6mKJ294cLxmi3khCUy2JJQp4htOYYkllSLUq6vudWJkTJiIuGdVR4bR6xuz2EvJLWHNg== +babel-preset-expo@54.0.10, babel-preset-expo@~54.0.10: + version "54.0.10" + resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz#3b70f4af3a5f65f945d7957ef511ee016e8f2fd6" + integrity sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw== dependencies: "@babel/helper-module-imports" "^7.25.9" "@babel/plugin-proposal-decorators" "^7.12.9" @@ -4532,11 +4861,6 @@ ci-info@^3.2.0, ci-info@^3.3.0, ci-info@^3.7.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -circular-dependency-plugin@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" - integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== - classnames@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" @@ -4786,15 +5110,22 @@ core-js-compat@^3.43.0: dependencies: browserslist "^4.28.0" +core-js-compat@^3.48.0: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6" + integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q== + dependencies: + browserslist "^4.28.1" + core-js-pure@^3.23.3: version "3.47.0" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.47.0.tgz#1104df8a3b6eb9189fcc559b5a65b90f66e7e887" integrity sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw== -core-js@^3.30.2: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.47.0.tgz#436ef07650e191afeb84c24481b298bd60eb4a17" - integrity sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg== +core-js@^3.48.0: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.48.0.tgz#1f813220a47bbf0e667e3885c36cd6f0593bf14d" + integrity sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ== core-util-is@~1.0.0: version "1.0.3" @@ -4851,19 +5182,19 @@ css-in-js-utils@^3.1.0: dependencies: hyphenate-style-name "^1.0.3" -css-loader@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.2.tgz#64671541c6efe06b0e22e750503106bdd86880f8" - integrity sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA== +css-loader@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.3.tgz#c0de715ceabe39b8531a85fcaf6734a430c4d99a" + integrity sha512-frbERmjT0UC5lMheWpJmMilnt9GEhbZJN/heUb7/zaJYeIzj5St9HvDcfshzzOqbsS+rYpMk++2SD3vGETDSyA== dependencies: icss-utils "^5.1.0" - postcss "^8.4.33" + postcss "^8.4.40" postcss-modules-extract-imports "^3.1.0" postcss-modules-local-by-default "^4.0.5" postcss-modules-scope "^3.2.0" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" - semver "^7.5.4" + semver "^7.6.3" css-select@^4.1.3: version "4.3.0" @@ -5179,13 +5510,13 @@ electron-to-chromium@^1.5.263: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== -electron@39.2.7: - version "39.2.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-39.2.7.tgz#1cf2371304994fe26c564764bd50878fe405fb4b" - integrity sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ== +electron@40.4.0: + version "40.4.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-40.4.0.tgz#e2e73d837df141216dcd91fb6c3f754e771387c2" + integrity sha512-31l4V7Ys4oUuXyaN/cCNnyBdDXN9RwOVOG+JhiHCf4zx5tZkHd43PKGY6KLEWpeYCxaphsuGSEjagJLfPqKj8g== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^22.7.7" + "@types/node" "^24.9.0" extract-zip "^2.0.1" emoji-datasource-apple@16.0.0: @@ -5225,13 +5556,13 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.17.4: - version "5.18.4" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz#c22d33055f3952035ce6a144ce092447c525f828" - integrity sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q== +enhanced-resolve@^5.19.0: + version "5.19.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz#6687446a15e969eaa63c2fa2694510e17ae6d97c" + integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg== dependencies: graceful-fs "^4.2.4" - tapable "^2.2.0" + tapable "^2.3.0" entities@^2.0.0: version "2.2.0" @@ -5593,11 +5924,13 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" - integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== +eslint-scope@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.0.tgz#dfcb41d6c0d73df6b977a50cf3e91c41ddb4154e" + integrity sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ== dependencies: + "@types/esrecurse" "^4.3.1" + "@types/estree" "^1.0.8" esrecurse "^4.3.0" estraverse "^5.2.0" @@ -5616,32 +5949,34 @@ eslint-visitor-keys@^4.2.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== -eslint@9.39.2: - version "9.39.2" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c" - integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== +eslint-visitor-keys@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz#b9aa1a74aa48c44b3ae46c1597ce7171246a94a9" + integrity sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q== + +eslint@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.0.0.tgz#c93c36a96d91621d0fbb680db848ea11af56ab1e" + integrity sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg== dependencies: "@eslint-community/eslint-utils" "^4.8.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.21.1" - "@eslint/config-helpers" "^0.4.2" - "@eslint/core" "^0.17.0" - "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.39.2" - "@eslint/plugin-kit" "^0.4.1" + "@eslint-community/regexpp" "^4.12.2" + "@eslint/config-array" "^0.23.0" + "@eslint/config-helpers" "^0.5.2" + "@eslint/core" "^1.1.0" + "@eslint/plugin-kit" "^0.6.0" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" ajv "^6.12.4" - chalk "^4.0.0" cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.4.0" - eslint-visitor-keys "^4.2.1" - espree "^10.4.0" - esquery "^1.5.0" + eslint-scope "^9.1.0" + eslint-visitor-keys "^5.0.0" + espree "^11.1.0" + esquery "^1.7.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^8.0.0" @@ -5651,29 +5986,28 @@ eslint@9.39.2: imurmurhash "^0.1.4" is-glob "^4.0.0" json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" + minimatch "^10.1.1" natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" - integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== +espree@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.1.0.tgz#7d0c82a69f8df670728dba256264b383fbf73e8f" + integrity sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw== dependencies: acorn "^8.15.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.1" + eslint-visitor-keys "^5.0.0" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== +esquery@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" @@ -5802,10 +6136,10 @@ expo-file-system@19.0.21, expo-file-system@~19.0.21: resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-19.0.21.tgz#e96a68107fb629cf0dd1906fe7b46b566ff13e10" integrity sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg== -expo-font@~14.0.10: - version "14.0.10" - resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-14.0.10.tgz#33fb9f6dc5661729192a6bc8cd6f08bd1a9097cc" - integrity sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q== +expo-font@~14.0.11: + version "14.0.11" + resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-14.0.11.tgz#198743d17332520545107df026d8a261e6b2732f" + integrity sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg== dependencies: fontfaceobserver "^2.1.0" @@ -5886,26 +6220,26 @@ expo-task-manager@14.0.9: dependencies: unimodules-app-loader "~6.0.8" -expo@54.0.31: - version "54.0.31" - resolved "https://registry.yarnpkg.com/expo/-/expo-54.0.31.tgz#c9b88d7154039bb7165abc21d73aec5e5fde6d71" - integrity sha512-kQ3RDqA/a59I7y+oqQGyrPbbYlgPMUdKBOgvFLpoHbD2bCM+F75i4N0mUijy7dG5F/CUCu2qHmGGUCXBbMDkCg== +expo@54.0.33: + version "54.0.33" + resolved "https://registry.yarnpkg.com/expo/-/expo-54.0.33.tgz#f7d572857323f5a8250a9afe245a487d2ee2735f" + integrity sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw== dependencies: "@babel/runtime" "^7.20.0" - "@expo/cli" "54.0.21" + "@expo/cli" "54.0.23" "@expo/config" "~12.0.13" "@expo/config-plugins" "~54.0.4" "@expo/devtools" "0.1.8" "@expo/fingerprint" "0.15.4" "@expo/metro" "~54.2.0" - "@expo/metro-config" "54.0.13" + "@expo/metro-config" "54.0.14" "@expo/vector-icons" "^15.0.3" "@ungap/structured-clone" "^1.3.0" - babel-preset-expo "~54.0.9" + babel-preset-expo "~54.0.10" expo-asset "~12.0.12" expo-constants "~18.0.13" expo-file-system "~19.0.21" - expo-font "~14.0.10" + expo-font "~14.0.11" expo-keep-awake "~15.0.8" expo-modules-autolinking "3.0.24" expo-modules-core "3.0.29" @@ -6225,9 +6559,9 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -framed-msgpack-rpc@keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer: +framed-msgpack-rpc@keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer-iserror2: version "1.1.24" - resolved "https://codeload.github.com/keybase/node-framed-msgpack-rpc/tar.gz/be19aba75ae0b67e301953efe87933aaaae7dfd8" + resolved "https://codeload.github.com/keybase/node-framed-msgpack-rpc/tar.gz/e3bd0029435879bdb6f5bfde5e05b097d3812886" dependencies: iced-error "0.0.13" iced-lock "2.0.1" @@ -6502,11 +6836,6 @@ global-dirs@^0.1.1: dependencies: ini "^1.3.4" -globals@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" - integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== - globalthis@^1.0.1, globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" @@ -6527,10 +6856,10 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -google-libphonenumber@3.2.43: - version "3.2.43" - resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.43.tgz#c1e5107ab9c6e3848dc2108e380bde08da80931c" - integrity sha512-TbIX/UC3BFRJwCxbBeCPwuRC4Qws9Jz/CECmfTM1t9RFoI3X6eRThurv6AYr9wSrt640IA9KFIHuAD/vlyjqRw== +google-libphonenumber@3.2.44: + version "3.2.44" + resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.44.tgz#694ec9d5581f013b881c4c2937791e973a45f420" + integrity sha512-9p2TghluF2LTChFMLWsDRD5N78SZDsILdUk4gyqYxBXluCyxoPiOq+Fqt7DKM+LUd33+OgRkdrc+cPR93AypCQ== gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" @@ -6712,10 +7041,10 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" -html-webpack-plugin@5.6.5: - version "5.6.5" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz#d57defb83cabbf29bf56b2d4bf10b67b650066be" - integrity sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g== +html-webpack-plugin@5.6.6: + version "5.6.6" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz#5321b9579f4a1949318550ced99c2a4a4e60cbaf" + integrity sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw== dependencies: "@types/html-minifier-terser" "^6.0.0" html-minifier-terser "^6.0.2" @@ -6882,12 +7211,12 @@ image-size@^1.0.2: dependencies: queue "6.0.2" -immer@11.1.3: - version "11.1.3" - resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.3.tgz#78681e1deb6cec39753acf04eb16d7576c04f4d6" - integrity sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q== +immer@11.1.4: + version "11.1.4" + resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.4.tgz#37aee86890b134a8f1a2fadd44361fb86c6ae67e" + integrity sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw== -import-fresh@^3.2.1, import-fresh@^3.3.0: +import-fresh@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== @@ -7750,11 +8079,6 @@ lodash.get@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" @@ -8989,7 +9313,7 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.33: +postcss@^8.4.40: version "8.5.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== @@ -9024,10 +9348,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.0.tgz#f72cf71505133f40cfa2ef77a2668cdc558fcd69" - integrity sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA== +prettier@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== prettier@^3.4.2: version "3.6.2" @@ -9314,10 +9638,10 @@ react-native-is-edge-to-edge@1.2.1, react-native-is-edge-to-edge@^1.2.1: "react-native-kb@file:../rnmodules/react-native-kb": version "0.1.1" -react-native-keyboard-controller@1.20.6: - version "1.20.6" - resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.6.tgz#139a66f36734a69648822bf2e7ec3c90e7b3dc8e" - integrity sha512-RS6FjIjTFtAMQGdcXp3m6jUs1XgDa8qkpO5c4ix1S5HS0z3L2E1LUOY5rD73YUADOO3MfQN1z3JkHdBtzKucbg== +react-native-keyboard-controller@1.20.7: + version "1.20.7" + resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.7.tgz#e1be1c15a5eb10b96a40a0812d8472e6e4bd8f29" + integrity sha512-G8S5jz1FufPrcL1vPtReATx+jJhT/j+sTqxMIb30b1z7cYEfMlkIzOCyaHgf6IMB2KA9uBmnA5M6ve2A9Ou4kw== dependencies: react-native-is-edge-to-edge "^1.2.1" @@ -9334,10 +9658,10 @@ react-native-safe-area-context@5.6.2: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz#283e006f5b434fb247fcb4be0971ad7473d5c560" integrity sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg== -react-native-screens@4.18.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.18.0.tgz#ba5a951b3f145e3b773d201143c19e1b1c1337ff" - integrity sha512-mRTLWL7Uc1p/RFNveEIIrhP22oxHduC2ZnLr/2iHwBeYpGXR0rJZ7Bgc0ktxQSHRjWTPT70qc/7yd4r9960PBQ== +react-native-screens@4.23.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.23.0.tgz#81574b1b0cc4ac6c9ed63e46eca7126f37affe86" + integrity sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw== dependencies: react-freeze "^1.0.0" warn-once "^0.1.0" @@ -9364,10 +9688,10 @@ react-native-webview@13.16.0: escape-string-regexp "^4.0.0" invariant "2.2.4" -react-native-worklets@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.7.1.tgz#263da5216b0b5342b9f1b36e0ab897c5ca5c863b" - integrity sha512-KNsvR48ULg73QhTlmwPbdJLPsWcyBotrGPsrDRDswb5FYpQaJEThUKc2ncXE4UM5dn/ewLoQHjSjLaKUVPxPhA== +react-native-worklets@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.7.3.tgz#e561c006b576019feb21888c4acf60261b55ca24" + integrity sha512-m/CIUCHvLQulboBn0BtgpsesXjOTeubU7t+V0lCPpBj0t2ExigwqDHoKj3ck7OeErnjgkD27wdAtQCubYATe3g== dependencies: "@babel/plugin-transform-arrow-functions" "7.27.1" "@babel/plugin-transform-class-properties" "7.27.1" @@ -9469,10 +9793,10 @@ react-test-renderer@19.1.0: react-is "^19.1.0" scheduler "^0.26.0" -react-window@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/react-window/-/react-window-2.2.5.tgz#425a29609980083aafd5a48a1711a2af9319c1d2" - integrity sha512-6viWvPSZvVuMIe9hrl4IIZoVfO/npiqOb03m4Z9w+VihmVzBbiudUrtUqDpsWdKvd/Ai31TCR25CBcFFAUm28w== +react-window@2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-2.2.6.tgz#00ca174346b5146d3c33a752d888181250c71d9f" + integrity sha512-v89O08xRdpCaEuf380B39D1C/0KgUDZA59xft6SVAjzjz/xQxSyXrgDWHymIsYI6TMrqE8WO+G0/PB9AGE8VNA== react@19.1.0: version "19.1.0" @@ -9710,7 +10034,7 @@ resolve.exports@^2.0.3: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.10, resolve@^1.22.2, resolve@^1.22.8: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.10, resolve@^1.22.11, resolve@^1.22.2, resolve@^1.22.8: version "1.22.11" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== @@ -9917,6 +10241,11 @@ semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.6.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + send@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" @@ -10470,11 +10799,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -10561,7 +10885,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.0.0, tapable@^2.2.0, tapable@^2.3.0: +tapable@^2.0.0, tapable@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== @@ -10830,15 +11154,15 @@ typedarray-to-buffer@4.0.0: resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz#cdd2933c61dd3f5f02eda5d012d441f95bfeb50a" integrity sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ== -typescript-eslint@8.53.0: - version "8.53.0" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.53.0.tgz#c35ca6403cd381753aee325f67e10d6101d55f04" - integrity sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw== +typescript-eslint@8.55.0: + version "8.55.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.55.0.tgz#abae8295c5f0f82f816218113a46e89bc30c3de2" + integrity sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw== dependencies: - "@typescript-eslint/eslint-plugin" "8.53.0" - "@typescript-eslint/parser" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" - "@typescript-eslint/utils" "8.53.0" + "@typescript-eslint/eslint-plugin" "8.55.0" + "@typescript-eslint/parser" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" + "@typescript-eslint/utils" "8.55.0" typescript@5.9.3: version "5.9.3" @@ -11062,10 +11386,10 @@ warn-once@^0.1.0, warn-once@^0.1.1: resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q== -watchpack@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" - integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== +watchpack@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -11178,10 +11502,10 @@ webpack-virtual-modules@^0.6.2: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== -webpack@5.104.1: - version "5.104.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.104.1.tgz#94bd41eb5dbf06e93be165ba8be41b8260d4fb1a" - integrity sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA== +webpack@5.105.2: + version "5.105.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.2.tgz#f3b76f9fc36f1152e156e63ffda3bbb82e6739ea" + integrity sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw== dependencies: "@types/eslint-scope" "^3.7.7" "@types/estree" "^1.0.8" @@ -11193,7 +11517,7 @@ webpack@5.104.1: acorn-import-phases "^1.0.3" browserslist "^4.28.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.4" + enhanced-resolve "^5.19.0" es-module-lexer "^2.0.0" eslint-scope "5.1.1" events "^3.2.0" @@ -11206,7 +11530,7 @@ webpack@5.104.1: schema-utils "^4.3.3" tapable "^2.3.0" terser-webpack-plugin "^5.3.16" - watchpack "^2.4.4" + watchpack "^2.5.1" webpack-sources "^3.3.3" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: @@ -11507,7 +11831,7 @@ zod@^3.22.4: resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.12.tgz#64f1ea53d00eab91853195653b5af9eee68970f0" integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ== -zustand@5.0.10: - version "5.0.10" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.10.tgz#4db510c0c4c25a5f1ae43227b307ddf1641a3210" - integrity sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg== +zustand@5.0.11: + version "5.0.11" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.11.tgz#99f912e590de1ca9ce6c6d1cab6cdb1f034ab494" + integrity sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==