import './tab-bar.css' import * as ConfigGen from '../actions/config-gen' import * as LoginGen from '../actions/login-gen' import * as Container from '../util/container' import * as FsConstants from '../constants/fs' import * as Kb from '../common-adapters' import * as Kbfs from '../fs/common' import * as Platforms from '../constants/platform' import * as ProfileGen from '../actions/profile-gen' import * as ProvisionGen from '../actions/provision-gen' import * as RPCTypes from '../constants/types/rpc-gen' import * as React from 'react' import * as RouteTreeGen from '../actions/route-tree-gen' import * as SettingsConstants from '../constants/settings' import * as SettingsGen from '../actions/settings-gen' import * as Styles from '../styles' import * as Tabs from '../constants/tabs' import * as TrackerConstants from '../constants/tracker2' import flags from '../util/feature-flags' import AccountSwitcher from './account-switcher/container' import RuntimeStats from '../app/runtime-stats' import InviteFriends from '../people/invite-friends/tab-bar-button' import HiddenString from '../util/hidden-string' import openURL from '../util/open-url' import {isLinux} from '../constants/platform' import {quit} from '../desktop/app/ctl.desktop' import {tabRoots} from './routes' export type Props = { navigation: any selectedTab: Tabs.AppTab } const data = { [Tabs.chatTab]: {icon: 'iconfont-nav-2-chat', label: 'Chat'}, [Tabs.cryptoTab]: {icon: 'iconfont-nav-2-crypto', label: 'Crypto'}, [Tabs.devicesTab]: {icon: 'iconfont-nav-2-devices', label: 'Devices'}, [Tabs.fsTab]: {icon: 'iconfont-nav-2-files', label: 'Files'}, [Tabs.gitTab]: {icon: 'iconfont-nav-2-git', label: 'Git'}, [Tabs.peopleTab]: {icon: 'iconfont-nav-2-people', label: 'People'}, [Tabs.settingsTab]: {icon: 'iconfont-nav-2-settings', label: 'Settings'}, [Tabs.teamsTab]: {icon: 'iconfont-nav-2-teams', label: 'Teams'}, [Tabs.walletsTab]: {icon: 'iconfont-nav-2-wallets', label: 'Wallet'}, } as const const tabs = Tabs.desktopTabOrder const FilesTabBadge = () => { const uploadIcon = FsConstants.getUploadIconForFilesTab(Container.useSelector(state => state.fs.badge)) return uploadIcon ? : null } const Header = () => { const dispatch = Container.useDispatch() const [showingMenu, setShowingMenu] = React.useState(false) const attachmentRef = React.useRef(null) const getAttachmentRef = () => attachmentRef.current const fullname = Container.useSelector( state => TrackerConstants.getDetails(state, state.config.username).fullname || '' ) const username = Container.useSelector(state => state.config.username) const onProfileClick = () => dispatch(ProfileGen.createShowUserProfile({username})) const onClickWrapper = () => { setShowingMenu(false) onProfileClick() } const onAddAccount = () => dispatch(ProvisionGen.createStartProvision()) const onHelp = () => openURL('https://book.keybase.io') const onQuit = () => { if (!__DEV__) { if (isLinux) { dispatch(SettingsGen.createStop({exitCode: RPCTypes.ExitCode.ok})) } else { dispatch(ConfigGen.createDumpLogs({reason: 'quitting through menu'})) } } // In case dump log doesn't exit for us Electron.remote.getCurrentWindow().hide() setTimeout(() => { quit() }, 2000) } const onSettings = () => dispatch(RouteTreeGen.createSwitchTab({tab: Tabs.settingsTab})) const onSignOut = () => dispatch(RouteTreeGen.createNavigateAppend({path: [SettingsConstants.logOutTab]})) const menuHeader = () => ( {fullname} } /> ) const menuItems = (): Kb.MenuItems => [ {onClick: onAddAccount, title: 'Log in as another user'}, {onClick: onSettings, title: 'Settings'}, {onClick: onHelp, title: 'Help'}, {danger: true, onClick: onSignOut, title: 'Sign out'}, {danger: true, onClick: onQuit, title: 'Quit Keybase'}, ] return ( <> setShowingMenu(true)}> <> Hi {username}! setShowingMenu(false)} /> ) } const keysMap = Tabs.desktopTabOrder.reduce((map, tab, index) => { map[`mod+${index + 1}`] = tab return map }, {}) const hotKeys = Object.keys(keysMap) const TabBar = (props: Props) => { const {selectedTab, navigation} = props const username = Container.useSelector(state => state.config.username) const badgeNumbers = Container.useSelector(state => state.notifications.navBadges) const fsCriticalUpdate = Container.useSelector(state => state.fs.criticalUpdate) const navRef = React.useRef(navigation.navigate) const onChangeTab = React.useCallback((tab: Tabs.AppTab) => { navRef.current(tab) }, []) const onNavUp = React.useCallback((tab: Tabs.AppTab) => { navRef.current(tabRoots[tab]) }, []) const onHotKey = React.useCallback((cmd: string) => { navRef.current(keysMap[cmd]) }, []) return username ? (
{tabs.map((t, i) => ( ))} {flags.inviteFriends && } ) : null } type TabProps = { tab: Tabs.AppTab index: number isSelected: boolean onTabClick: (t: Tabs.AppTab) => void badge?: number } const Tab = React.memo((props: TabProps) => { const {tab, index, isSelected, onTabClick, badge} = props const {label} = data[tab] const dispatch = Container.useDispatch() const accountRows = Container.useSelector(state => state.config.configuredAccounts) const current = Container.useSelector(state => state.config.username) const onQuickSwitch = React.useMemo( () => index === 0 ? () => { const row = accountRows.find(a => a.username !== current && a.hasStoredSecret) if (row) { dispatch(ConfigGen.createSetUserSwitching({userSwitching: true})) dispatch(LoginGen.createLogin({password: new HiddenString(''), username: row.username})) } else { onTabClick(tab) } } : undefined, [accountRows, dispatch, index, current, onTabClick, tab] ) // no long press on desktop so a quick version const [mouseTime, setMouseTime] = React.useState(0) const onMouseUp = React.useMemo( () => index === 0 ? () => { if (mouseTime && Date.now() - mouseTime > 1000) { onQuickSwitch?.() } setMouseTime(0) } : undefined, [index, onQuickSwitch, mouseTime] ) const onMouseDown = React.useMemo( () => index === 0 ? () => { setMouseTime(Date.now()) } : undefined, [index] ) const onMouseLeave = React.useMemo( () => index === 0 ? () => { setMouseTime(0) } : undefined, [index] ) return ( onTabClick(tab)} onMouseDown={onMouseDown} onMouseUp={onMouseUp} onMouseLeave={onMouseLeave} > {tab === Tabs.fsTab && } {label} {!!badge && } ) }) const styles = Styles.styleSheetCreate( () => ({ avatar: {marginLeft: 14}, badgeIcon: { bottom: -4, position: 'absolute', right: 8, }, badgeIconUpload: { bottom: -Styles.globalMargins.xxtiny, height: Styles.globalMargins.xsmall, position: 'absolute', right: Styles.globalMargins.xsmall, width: Styles.globalMargins.xsmall, }, button: { margin: Styles.globalMargins.xsmall, }, caret: {marginRight: 12}, divider: {marginTop: Styles.globalMargins.tiny}, fullname: {maxWidth: 180}, header: {flexShrink: 0, height: 80, marginBottom: 20}, headerBox: { paddingTop: Styles.globalMargins.small, }, iconBox: { justifyContent: 'flex-end', position: 'relative', }, menu: {marginLeft: Styles.globalMargins.tiny}, nameContainer: {height: 24}, osButtons: Styles.platformStyles({ isElectron: { ...Styles.desktopStyles.windowDragging, flexGrow: 1, }, }), tab: { alignItems: 'center', paddingRight: 12, position: 'relative', }, username: Styles.platformStyles({ isElectron: {color: Styles.globalColors.blueLighter, flexGrow: 1, wordBreak: 'break-all'}, }), } as const) ) export default TabBar