<commit_before>import {Layer} from '@sanity/ui'
import {StructureBuilder as S} from '@sanity/structure'
import {InitialValueTemplateItem} from '@sanity/structure/lib/InitialValueTemplateItem'
import classNames from 'classnames'
import {negate} from 'lodash'
import {MenuButton} from 'part:@sanity/components/menu-button'
import Menu from 'part:@sanity/components/menus/default'
import IconMoreVert from 'part:@sanity/base/more-vert-icon'
import {IntentLink} from 'part:@sanity/base/router'
import Button from 'part:@sanity/components/buttons/default'
import IntentButton from 'part:@sanity/components/buttons/intent'
import TabPanel from 'part:@sanity/components/tabs/tab-panel'
import React from 'react'
import {ScrollContainer} from '../../../../components/scroll'
import {childrenToElementArray} from '../helpers'
import {MenuItem, MenuItemGroup} from '../menus/types'
import Styleable from '../utilities/Styleable'

import {LegacyLayerProvider} from '../../../../components'
import defaultStyles from './DefaultPane.css'

interface DefaultPaneProps {
  color?: 'success' | 'warning' | 'danger'
  hasTabs?: boolean
  tabIdPrefix?: string
  viewId?: string
  title?: React.ReactNode
  isCollapsed?: boolean
  onExpand?: (index: number) => void
  onCollapse?: (index: number) => void
  children?: React.ReactNode
  isLoading?: boolean
  isSelected?: boolean
  isScrollable?: boolean
  hasSiblings?: boolean
  onAction?: (item: MenuItem) => boolean
  renderActions?: (actions: MenuItem[]) => React.ReactNode
  menuItems?: MenuItem[]
  menuItemGroups?: MenuItemGroup[]
  initialValueTemplates?: InitialValueTemplateItem[]
  index: number
  footer?: React.ReactNode
  renderHeaderViewMenu?: () => React.ReactNode
  styles?: Record<string, string>
}

interface State {
  isInitialValueMenuOpen: boolean
  isMenuOpen: boolean
}

function getActionKey(action: MenuItem, index: number) {
  const title = String(action.title)

  return (typeof action.action === 'string' ? action.action + title : title) || index
}

function noActionFn() {
  // eslint-disable-next-line no-console
  console.warn('No handler defined for action')
}

const noop = () => {
  /* intentional noop */
}

const isActionButton = (item: MenuItem) => Boolean(item.showAsAction)
const isMenuButton = negate(isActionButton)

class DefaultPane extends React.PureComponent<DefaultPaneProps, State> {
  static defaultProps = {
    index: 0,
    footer: undefined,
    hasTabs: false,
    tabIdPrefix: undefined,
    viewId: undefined,
    title: 'Untitled',
    hasSiblings: false,
    isCollapsed: false,
    isSelected: false,
    isScrollable: true,
    renderActions: undefined,
    styles: {},
    children: <div />,
    onAction: noop,
    menuItems: [],
    menuItemGroups: [],
    initialValueTemplates: [],
    renderHeaderViewMenu: () => null,
  }

  actionHandlers = {}

  rootElement: HTMLDivElement | null = null

  templateMenuId: string
  paneMenuId: string

  constructor(props: DefaultPaneProps) {
    super(props)

    this.state = {
      isMenuOpen: false,
      isInitialValueMenuOpen: false,
    }

    // Passed to rendered <Menu> components. This prevents the "click outside"
    // functionality from kicking in when pressing the toggle menu button
    this.templateMenuId = Math.random().toString(36).substr(2, 6)

    this.paneMenuId = Math.random().toString(36).substr(2, 6)
  }

  // Triggered by clicking "outside" of the menu when open, or after triggering action
  handleCloseContextMenu = () => {
    this.setState({isMenuOpen: false})
  }

  setContextMenuOpen = (val: boolean) => {
    this.setState({isMenuOpen: val})
  }

  setInitialValueMenuOpen = (val: boolean) => {
    this.setState({isInitialValueMenuOpen: val})
  }

  handleCloseTemplateMenu = () => {
    this.setState({isInitialValueMenuOpen: false})
  }

  handleRootClick = () => {
    const {onExpand, isCollapsed, index} = this.props

    if (isCollapsed && onExpand) {
      onExpand(index)
    }
  }

  handleTitleClick = () => {
    const {onCollapse, isCollapsed, index} = this.props
    if (!isCollapsed && onCollapse) {
      onCollapse(index)
    }
  }

  handleMenuAction = (item: MenuItem) => {
    this.setContextMenuOpen(false)

    if (typeof item.action === 'function') {
      item.action(item.params)
      return
    }

    const {onAction} = this.props

    const actionHandled = onAction && onAction(item)

    if (actionHandled) {
      return
    }

    const handler =
      (typeof item.action === 'string' && this.actionHandlers[item.action]) || noActionFn

    handler(item.params, this)
  }

  renderIntentAction = (action: MenuItem, i: number): React.ReactElement => {
    const {styles = {}} = this.props

    return (
      <IntentButton
        className={styles.actionButton}
        icon={action.icon}
        intent={action.intent && action.intent.type}
        key={getActionKey(action, i)}
        kind="simple"
        padding="small"
        params={action.intent && action.intent.params}
        title={typeof action.title === 'string' ? action.title : undefined}
      />
    )
  }

  renderActionMenuItem = (item: MenuItem, index: number) => {
    if (!item) return null

    const {styles = {}} = this.props
    const params = item.intent && item.intent.params
    const Icon = item.icon

    return (
      <IntentLink
        className={styles.initialValueTemplateMenuItem}
        intent="create"
        key={item.key || index}
        onClick={this.handleCloseTemplateMenu}
        params={params}
      >
        <div>
          <div>{item.title}</div>
          {typeof params === 'object' && (
            // @todo: typings
            <div className={styles.initialValueTemplateSubtitle}>{(params as any).type}</div>
          )}
        </div>
        {Icon && (
          <div className={styles.initialValueTemplateMenuItemIcon}>
            <Icon />
          </div>
        )}
      </IntentLink>
    )
  }

  renderAction = (action: MenuItem, i: number): React.ReactNode => {
    if (action.intent) {
      return this.renderIntentAction(action, i)
    }

    const styles = this.props.styles || {}

    // @todo: typings
    const items: MenuItem[] = this.props.initialValueTemplates
      ? (S.menuItemsFromInitialValueTemplateItems(this.props.initialValueTemplates) as any)
      : []

    const Icon = action.icon

    return (
      <div className={styles.menuWrapper} key={getActionKey(action, i)}>
        {action.action !== 'toggleTemplateSelectionMenu' && (
          <Button
            data-menu-button-id={this.templateMenuId}
            icon={Icon}
            kind="simple"
            padding="small"
            title={typeof action.title === 'string' ? action.title : undefined}
            onClick={this.handleMenuAction.bind(this, action)}
          />
        )}

        {action.action === 'toggleTemplateSelectionMenu' && (
          <MenuButton
            boundaryElement={this.rootElement}
            buttonProps={{
              'aria-label': 'Menu',
              'aria-haspopup': 'menu',
              'aria-expanded': this.state.isInitialValueMenuOpen,
              'aria-controls': this.templateMenuId,
              icon: Icon,
              kind: 'simple',
              padding: 'small',
              selected: this.state.isInitialValueMenuOpen,
              title: 'Create new document',
            }}
            menu={
              <div className={styles.initialValueTemplateMenu}>
                {items.map((item, index) => this.renderActionMenuItem(item, index))}
              </div>
            }
            open={this.state.isInitialValueMenuOpen}
            placement="bottom"
            setOpen={this.setInitialValueMenuOpen}
          />
        )}
      </div>
    )
  }<commit_msg>feat(base): add basic permission check to default pane menu<commit_after>import {Box, Layer, Tooltip} from '@sanity/ui'
import {StructureBuilder as S} from '@sanity/structure'
import {InitialValueTemplateItem} from '@sanity/structure/lib/InitialValueTemplateItem'
import classNames from 'classnames'
import {negate} from 'lodash'
import {MenuButton} from 'part:@sanity/components/menu-button'
import Menu from 'part:@sanity/components/menus/default'
import IconMoreVert from 'part:@sanity/base/more-vert-icon'
import {IntentLink} from 'part:@sanity/base/router'
import Button from 'part:@sanity/components/buttons/default'
import IntentButton from 'part:@sanity/components/buttons/intent'
import TabPanel from 'part:@sanity/components/tabs/tab-panel'
import grantsStore from 'part:@sanity/base/grants'
import userStore from 'part:@sanity/base/user'
import {tap} from 'rxjs/operators'
import React from 'react'
import {combineLatest} from 'rxjs'
import type {Subscription} from 'rxjs'
import {ScrollContainer} from '../../../../components/scroll'
import {childrenToElementArray} from '../helpers'
import {MenuItem, MenuItemGroup} from '../menus/types'
import Styleable from '../utilities/Styleable'

import {LegacyLayerProvider, InsufficientPermissionsMessage} from '../../../../components'
import defaultStyles from './DefaultPane.css'

interface DefaultPaneProps {
  color?: 'success' | 'warning' | 'danger'
  hasTabs?: boolean
  tabIdPrefix?: string
  viewId?: string
  title?: React.ReactNode
  isCollapsed?: boolean
  onExpand?: (index: number) => void
  onCollapse?: (index: number) => void
  children?: React.ReactNode
  isLoading?: boolean
  isSelected?: boolean
  isScrollable?: boolean
  hasSiblings?: boolean
  onAction?: (item: MenuItem) => boolean
  renderActions?: (actions: MenuItem[]) => React.ReactNode
  menuItems?: MenuItem[]
  menuItemGroups?: MenuItemGroup[]
  initialValueTemplates?: InitialValueTemplateItem[]
  index: number
  footer?: React.ReactNode
  renderHeaderViewMenu?: () => React.ReactNode
  styles?: Record<string, string>
}

interface PermissionCheckResult {
  granted: boolean
  reason: string
}

interface State {
  isInitialValueMenuOpen: boolean
  isMenuOpen: boolean
  createPermission?: PermissionCheckResult
  currentUser?: any
}

function getActionKey(action: MenuItem, index: number) {
  const title = String(action.title)

  return (typeof action.action === 'string' ? action.action + title : title) || index
}

function noActionFn() {
  // eslint-disable-next-line no-console
  console.warn('No handler defined for action')
}

const noop = () => {
  /* intentional noop */
}

const isActionButton = (item: MenuItem) => Boolean(item.showAsAction)
const isMenuButton = negate(isActionButton)

class DefaultPane extends React.PureComponent<DefaultPaneProps, State> {
  static defaultProps = {
    index: 0,
    footer: undefined,
    hasTabs: false,
    tabIdPrefix: undefined,
    viewId: undefined,
    title: 'Untitled',
    hasSiblings: false,
    isCollapsed: false,
    isSelected: false,
    isScrollable: true,
    renderActions: undefined,
    styles: {},
    children: <div />,
    onAction: noop,
    menuItems: [],
    menuItemGroups: [],
    initialValueTemplates: [],
    renderHeaderViewMenu: () => null,
  }

  actionHandlers = {}

  rootElement: HTMLDivElement | null = null

  templateMenuId: string
  paneMenuId: string
  subscription: Subscription

  constructor(props: DefaultPaneProps) {
    super(props)

    this.state = {
      isMenuOpen: false,
      isInitialValueMenuOpen: false,
    }

    // Passed to rendered <Menu> components. This prevents the "click outside"
    // functionality from kicking in when pressing the toggle menu button
    this.templateMenuId = Math.random().toString(36).substr(2, 6)

    this.paneMenuId = Math.random().toString(36).substr(2, 6)
  }

  componentDidMount() {
    this.subscription = combineLatest(
      grantsStore.checkDocumentPermission('create', {_type: 'any', _id: 'any'}),
      userStore.me
    )
      .pipe(
        tap(([permission, currentUser]) => {
          this.setState({
            createPermission: permission as PermissionCheckResult,
            currentUser: currentUser,
          })
        })
      )
      .subscribe()
  }

  componentWillUnmount() {
    this.subscription?.unsubscribe()
  }

  // Triggered by clicking "outside" of the menu when open, or after triggering action
  handleCloseContextMenu = () => {
    this.setState({isMenuOpen: false})
  }

  setContextMenuOpen = (val: boolean) => {
    this.setState({isMenuOpen: val})
  }

  setInitialValueMenuOpen = (val: boolean) => {
    this.setState({isInitialValueMenuOpen: val})
  }

  handleCloseTemplateMenu = () => {
    this.setState({isInitialValueMenuOpen: false})
  }

  handleRootClick = () => {
    const {onExpand, isCollapsed, index} = this.props

    if (isCollapsed && onExpand) {
      onExpand(index)
    }
  }

  handleTitleClick = () => {
    const {onCollapse, isCollapsed, index} = this.props
    if (!isCollapsed && onCollapse) {
      onCollapse(index)
    }
  }

  handleMenuAction = (item: MenuItem) => {
    this.setContextMenuOpen(false)

    if (typeof item.action === 'function') {
      item.action(item.params)
      return
    }

    const {onAction} = this.props

    const actionHandled = onAction && onAction(item)

    if (actionHandled) {
      return
    }

    const handler =
      (typeof item.action === 'string' && this.actionHandlers[item.action]) || noActionFn

    handler(item.params, this)
  }

  renderIntentAction = (action: MenuItem, i: number): React.ReactElement => {
    const {styles = {}} = this.props
    const {currentUser, createPermission} = this.state

    return (
      <Layer zOffset={100000}>
        <Tooltip
          disabled={createPermission?.granted}
          content={
            <Box padding={2} style={{maxWidth: 250}}>
              <InsufficientPermissionsMessage
                currentUser={currentUser}
                operationLabel="create new documents"
              />
            </Box>
          }
          portal
        >
          <div>
            <IntentButton
              className={styles.actionButton}
              icon={action.icon}
              disabled={!createPermission?.granted}
              intent={action.intent && action.intent.type}
              key={getActionKey(action, i)}
              kind="simple"
              padding="small"
              params={action.intent && action.intent.params}
              title={
                createPermission?.granted && typeof action.title === 'string'
                  ? action.title
                  : undefined
              }
            />
          </div>
        </Tooltip>
      </Layer>
    )
  }

  renderActionMenuItem = (item: MenuItem, index: number) => {
    if (!item) return null

    const {styles = {}} = this.props
    const params = item.intent && item.intent.params
    const Icon = item.icon

    return (
      <IntentLink
        className={styles.initialValueTemplateMenuItem}
        intent="create"
        key={item.key || index}
        onClick={this.handleCloseTemplateMenu}
        params={params}
      >
        <div>
          <div>{item.title}</div>
          {typeof params === 'object' && (
            // @todo: typings
            <div className={styles.initialValueTemplateSubtitle}>{(params as any).type}</div>
          )}
        </div>
        {Icon && (
          <div className={styles.initialValueTemplateMenuItemIcon}>
            <Icon />
          </div>
        )}
      </IntentLink>
    )
  }

  renderAction = (action: MenuItem, i: number): React.ReactNode => {
    if (action.intent) {
      return this.renderIntentAction(action, i)
    }
    const {currentUser, createPermission} = this.state

    const styles = this.props.styles || {}

    // @todo: typings
    const items: MenuItem[] = this.props.initialValueTemplates
      ? (S.menuItemsFromInitialValueTemplateItems(this.props.initialValueTemplates) as any)
      : []

    const Icon = action.icon

    return (
      <div className={styles.menuWrapper} key={getActionKey(action, i)}>
        {action.action !== 'toggleTemplateSelectionMenu' && (
          <Button
            data-menu-button-id={this.templateMenuId}
            icon={Icon}
            kind="simple"
            padding="small"
            title={typeof action.title === 'string' ? action.title : undefined}
            onClick={this.handleMenuAction.bind(this, action)}
          />
        )}

        {action.action === 'toggleTemplateSelectionMenu' && (
          <Layer zOffset={100000}>
            <Tooltip
              disabled={createPermission?.granted}
              content={
                <Box padding={2} style={{maxWidth: 250}}>
                  <InsufficientPermissionsMessage
                    currentUser={currentUser}
                    operationLabel="create new documents"
                  />
                </Box>
              }
              portal
            >
              <div>
                <MenuButton
                  boundaryElement={this.rootElement}
                  buttonProps={{
                    'aria-label': 'Menu',
                    'aria-haspopup': 'menu',
                    'aria-expanded': this.state.isInitialValueMenuOpen,
                    'aria-controls': this.templateMenuId,
                    icon: Icon,
                    kind: 'simple',
                    padding: 'small',
                    selected: this.state.isInitialValueMenuOpen,
                    title: 'Create new document',
                    disabled: !createPermission?.granted,
                  }}
                  menu={
                    <div className={styles.initialValueTemplateMenu}>
                      {items.map((item, index) => this.renderActionMenuItem(item, index))}
                    </div>
                  }
                  open={this.state.isInitialValueMenuOpen}
                  placement="bottom"
                  setOpen={this.setInitialValueMenuOpen}
                />
              </div>
            </Tooltip>
          </Layer>
        )}
      </div>
    )
  }
{'fraction_non_alphanumeric': 0.1234808785817817, 'fraction_numerical': 0.0021184078492585572, 'mean_word_length': 2.5607383882493053, 'pattern_counts': {'":': 0, '<': 65, '<?xml version=': 0, '>': 104, 'https://': 0, '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': '421061', 'n_tokens_mistral': 5128, 'n_tokens_neox': 5015, 'n_words': 1103}