<commit_before>import * as React from 'react'
import * as Container from '../util/container'
import * as Kb from '../common-adapters'
import * as FsConstants from '../constants/fs'
import * as FsTypes from '../constants/types/fs'
import * as GregorGen from '../actions/gregor-gen'
import * as TeamsGen from '../actions/teams-gen'
import Teams, {OwnProps as MainOwnProps} from './main'
import {HeaderRightActions} from './main/header'
import openURL from '../util/open-url'
import * as Constants from '../constants/teams'
import * as WaitingConstants from '../constants/waiting'
import * as Types from '../constants/types/teams'
import {memoize} from '../util/memoize'
import {useTeamsSubscribe} from './subscriber'
import {useNavigationEvents} from '../util/navigation-hooks'

type OwnProps = {}

// share some between headerRightActions on desktop and component on mobile
type HeaderActionProps = {
  onCreateTeam: () => void
  onJoinTeam: () => void
}
const useHeaderActions = (): HeaderActionProps => {
  const dispatch = Container.useDispatch()
  const nav = Container.useSafeNavigation()
  return {
    onCreateTeam: () => {
      dispatch(
        nav.safeNavigateAppendPayload({
          path: ['teamNewTeamDialog'],
        })
      )
    },
    onJoinTeam: () => {
      dispatch(nav.safeNavigateAppendPayload({path: ['teamJoinTeamDialog']}))
    },
  }
}

const orderTeams = memoize((teams: Types.State['teamDetails']) =>
  [...teams.values()].sort((a, b) => a.teamname.localeCompare(b.teamname))
)

type ReloadableProps = Omit<MainOwnProps, 'onManageChat' | 'onViewTeam'>

const Reloadable = (
  props: ReloadableProps & {
    loadTeams: () => void
    onClearBadges: () => void
  }
) => {
  // On desktop, clear the badges upon navigating away from this tab. This is more reliable than nav events.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  React.useEffect(() => () => props.onClearBadges(), [])
  // Since this component does not unmount on mobile, also clear badge with nav events.
  useNavigationEvents(e => {
    if (e.type === 'willBlur') {
      props.onClearBadges()
    }
  })

  // subscribe to teams changes
  useTeamsSubscribe()

  const {loadTeams, onClearBadges, ...rest} = props
  const headerActions = useHeaderActions()

  const dispatch = Container.useDispatch()
  const nav = Container.useSafeNavigation()
  const otherActions = {
    onManageChat: (teamID: Types.TeamID) =>
      dispatch(nav.safeNavigateAppendPayload({path: [{props: {teamID}, selected: 'chatManageChannels'}]})),
    onViewTeam: (teamID: Types.TeamID) =>
      dispatch(nav.safeNavigateAppendPayload({path: [{props: {teamID}, selected: 'team'}]})),
  }

  return (
    <Kb.Reloadable waitingKeys={Constants.teamsLoadedWaitingKey} onReload={loadTeams}>
      <Teams {...rest} {...headerActions} {...otherActions} />
    </Kb.Reloadable>
  )
}
Reloadable.navigationOptions = {
  header: undefined,
  headerRightActions: () => <ConnectedHeaderRightActions />,
  title: 'Teams',
}

const Connected = Container.connect(
  (state: Container.TypedState) => ({
    _teamresetusers: state.teams.teamIDToResetUsers || new Map(),
    _teams: state.teams.teamDetails,
    deletedTeams: state.teams.deletedTeams,
    loaded: !WaitingConstants.anyWaiting(state, Constants.teamsLoadedWaitingKey),
    newTeamRequests: state.teams.newTeamRequests,
    newTeams: state.teams.newTeams,
    sawChatBanner: state.teams.sawChatBanner || false,
  }),
  (dispatch: Container.TypedDispatch) => ({
    loadTeams: () => dispatch(TeamsGen.createGetTeams()),
    onClearBadges: () => dispatch(TeamsGen.createClearNavBadges()),
    onHideChatBanner: () =>
      dispatch(GregorGen.createUpdateCategory({body: 'true', category: 'sawChatBanner'})),
    onOpenFolder: (teamname: Types.Teamname) =>
      dispatch(
        FsConstants.makeActionForOpenPathInFilesTab(FsTypes.stringToPath(`/keybase/team/${teamname}`))
      ),
    onReadMore: () => {
      openURL('https://keybase.io/blog/introducing-keybase-teams')
    },
  }),
  (stateProps, dispatchProps, _: OwnProps) => ({
    deletedTeams: stateProps.deletedTeams,
    loaded: stateProps.loaded,
    newTeamRequests: stateProps.newTeamRequests,
    newTeams: stateProps.newTeams,
    sawChatBanner: stateProps.sawChatBanner,
    teamresetusers: stateProps._teamresetusers,
    teams: orderTeams(stateProps._teams),
    ...dispatchProps,
  })
)(Reloadable)

const ConnectedHeaderRightActions = (_: {}) => {
  const actions = useHeaderActions()
  return <HeaderRightActions {...actions} />
}

export default Connected<commit_msg>Show teams with new notification requests first in team list (#21904)<commit_after>import * as React from 'react'
import * as Container from '../util/container'
import * as Kb from '../common-adapters'
import * as FsConstants from '../constants/fs'
import * as FsTypes from '../constants/types/fs'
import * as GregorGen from '../actions/gregor-gen'
import * as TeamsGen from '../actions/teams-gen'
import Teams, {OwnProps as MainOwnProps} from './main'
import {HeaderRightActions} from './main/header'
import openURL from '../util/open-url'
import * as Constants from '../constants/teams'
import * as WaitingConstants from '../constants/waiting'
import * as Types from '../constants/types/teams'
import {memoize} from '../util/memoize'
import {useTeamsSubscribe} from './subscriber'
import {useNavigationEvents} from '../util/navigation-hooks'

type OwnProps = {}

// share some between headerRightActions on desktop and component on mobile
type HeaderActionProps = {
  onCreateTeam: () => void
  onJoinTeam: () => void
}
const useHeaderActions = (): HeaderActionProps => {
  const dispatch = Container.useDispatch()
  const nav = Container.useSafeNavigation()
  return {
    onCreateTeam: () => {
      dispatch(
        nav.safeNavigateAppendPayload({
          path: ['teamNewTeamDialog'],
        })
      )
    },
    onJoinTeam: () => {
      dispatch(nav.safeNavigateAppendPayload({path: ['teamJoinTeamDialog']}))
    },
  }
}

const orderTeamsImpl = (
  teams: Map<string, Types.TeamDetails>,
  newRequests: Map<Types.TeamID, number>
): Array<Types.TeamDetails> =>
  [...teams.values()].sort((a, b) => {
    const sizeDiff = (newRequests.get(b.id) || 0) - (newRequests.get(a.id) || 0)
    if (sizeDiff != 0) return sizeDiff
    return a.teamname.localeCompare(b.teamname)
  })

const orderTeams = memoize(orderTeamsImpl)

type ReloadableProps = Omit<MainOwnProps, 'onManageChat' | 'onViewTeam'>

const Reloadable = (
  props: ReloadableProps & {
    loadTeams: () => void
    onClearBadges: () => void
  }
) => {
  // On desktop, clear the badges upon navigating away from this tab. This is more reliable than nav events.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  React.useEffect(() => () => props.onClearBadges(), [])
  // Since this component does not unmount on mobile, also clear badge with nav events.
  useNavigationEvents(e => {
    if (e.type === 'willBlur') {
      props.onClearBadges()
    }
  })

  // subscribe to teams changes
  useTeamsSubscribe()

  const {loadTeams, onClearBadges, ...rest} = props
  const headerActions = useHeaderActions()

  const dispatch = Container.useDispatch()
  const nav = Container.useSafeNavigation()
  const otherActions = {
    onManageChat: (teamID: Types.TeamID) =>
      dispatch(nav.safeNavigateAppendPayload({path: [{props: {teamID}, selected: 'chatManageChannels'}]})),
    onViewTeam: (teamID: Types.TeamID) =>
      dispatch(nav.safeNavigateAppendPayload({path: [{props: {teamID}, selected: 'team'}]})),
  }

  return (
    <Kb.Reloadable waitingKeys={Constants.teamsLoadedWaitingKey} onReload={loadTeams}>
      <Teams {...rest} {...headerActions} {...otherActions} />
    </Kb.Reloadable>
  )
}
Reloadable.navigationOptions = {
  header: undefined,
  headerRightActions: () => <ConnectedHeaderRightActions />,
  title: 'Teams',
}

const Connected = Container.connect(
  (state: Container.TypedState) => ({
    _teamresetusers: state.teams.teamIDToResetUsers || new Map(),
    _teams: state.teams.teamDetails,
    deletedTeams: state.teams.deletedTeams,
    loaded: !WaitingConstants.anyWaiting(state, Constants.teamsLoadedWaitingKey),
    newTeamRequests: state.teams.newTeamRequests,
    newTeams: state.teams.newTeams,
    sawChatBanner: state.teams.sawChatBanner || false,
  }),
  (dispatch: Container.TypedDispatch) => ({
    loadTeams: () => dispatch(TeamsGen.createGetTeams()),
    onClearBadges: () => dispatch(TeamsGen.createClearNavBadges()),
    onHideChatBanner: () =>
      dispatch(GregorGen.createUpdateCategory({body: 'true', category: 'sawChatBanner'})),
    onOpenFolder: (teamname: Types.Teamname) =>
      dispatch(
        FsConstants.makeActionForOpenPathInFilesTab(FsTypes.stringToPath(`/keybase/team/${teamname}`))
      ),
    onReadMore: () => {
      openURL('https://keybase.io/blog/introducing-keybase-teams')
    },
  }),
  (stateProps, dispatchProps, _: OwnProps) => ({
    deletedTeams: stateProps.deletedTeams,
    loaded: stateProps.loaded,
    newTeamRequests: stateProps.newTeamRequests,
    newTeams: stateProps.newTeams,
    sawChatBanner: stateProps.sawChatBanner,
    teamresetusers: stateProps._teamresetusers,
    teams: orderTeams(stateProps._teams, stateProps.newTeamRequests),
    ...dispatchProps,
  })
)(Reloadable)

const ConnectedHeaderRightActions = (_: {}) => {
  const actions = useHeaderActions()
  return <HeaderRightActions {...actions} />
}

export default Connected
{'fraction_non_alphanumeric': 0.14237467018469657, 'fraction_numerical': 0.0008443271767810027, 'mean_word_length': 4.045793397231097, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 68, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '893857', 'n_tokens_mistral': 2972, 'n_tokens_neox': 2976, 'n_words': 626}