tabs-container.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
  2. import {
  3. TAB_TRANSFER_TYPE,
  4. useTabsContext,
  5. } from '@/features/ide-react/context/tabs-context'
  6. import { Tab } from './tab'
  7. import { TabsContextMenu } from './tabs-context-menu'
  8. import SplitTestBadge from '@/shared/components/split-test-badge'
  9. import { useCallback, useMemo, useState } from 'react'
  10. import { throttle } from 'lodash'
  11. import { debugConsole } from '@/utils/debugging'
  12. import classNames from 'classnames'
  13. export const TabsContainer = () => {
  14. const {
  15. tabs,
  16. openTab,
  17. closeTab,
  18. moveTab,
  19. makeTabPermanent,
  20. setContextMenuTarget,
  21. setHeaderSlot,
  22. } = useTabsContext()
  23. const { openEntity } = useFileTreeOpenContext()
  24. const [hovered, setHovered] = useState<boolean>(false)
  25. const openContextMenu = useCallback(
  26. (coords: { top: number; left: number }, tabId: string) => {
  27. setContextMenuTarget({ ...coords, tabId })
  28. },
  29. [setContextMenuTarget]
  30. )
  31. const closeContextMenu = useCallback(() => {
  32. setContextMenuTarget(null)
  33. }, [setContextMenuTarget])
  34. const throttledOnDragOver = useMemo(
  35. () =>
  36. throttle(() => {
  37. setHovered(true)
  38. }, 50),
  39. []
  40. )
  41. const onDragOver = useCallback(
  42. (e: React.DragEvent) => {
  43. e.preventDefault()
  44. throttledOnDragOver()
  45. },
  46. [throttledOnDragOver]
  47. )
  48. const onDrop = useCallback(
  49. (e: React.DragEvent) => {
  50. throttledOnDragOver.cancel()
  51. e.stopPropagation()
  52. e.preventDefault()
  53. setHovered(false)
  54. const draggedTabId = e.dataTransfer.getData(TAB_TRANSFER_TYPE)
  55. if (!draggedTabId) {
  56. debugConsole.warn('No dragged tab id found in dataTransfer')
  57. return
  58. }
  59. const targetTabId = tabs[tabs.length - 1]?.id
  60. if (!targetTabId) {
  61. debugConsole.warn('No target tab id found for drop')
  62. return
  63. }
  64. moveTab(draggedTabId, targetTabId, 'right')
  65. },
  66. [tabs, moveTab, throttledOnDragOver]
  67. )
  68. const onDragLeave = useCallback(() => {
  69. throttledOnDragOver.cancel()
  70. setHovered(false)
  71. }, [throttledOnDragOver])
  72. const onWheel = useCallback((e: React.WheelEvent) => {
  73. if (e.deltaY !== 0 && e.deltaX === 0) {
  74. // if this is a purely vertical scroll, convert it to a horizontal scroll
  75. // instead
  76. e.currentTarget.scrollLeft += e.deltaY
  77. }
  78. }, [])
  79. return (
  80. <div className="editor-tabs-container">
  81. <div className="review-panel-header-slot" ref={setHeaderSlot} />
  82. <div
  83. className={classNames('editor-tabs-row', {
  84. 'editor-tabs-row-hovered': hovered,
  85. })}
  86. role="tablist"
  87. onDragOver={onDragOver}
  88. onDrop={onDrop}
  89. onDragLeave={onDragLeave}
  90. onWheel={onWheel}
  91. tabIndex={-1}
  92. >
  93. {tabs.map(tab => (
  94. <Tab
  95. key={tab.id}
  96. tab={tab}
  97. openTab={openTab}
  98. closeTab={closeTab}
  99. isSelected={openEntity?.entity._id === tab.id}
  100. onTabDrop={moveTab}
  101. makeTabPermanent={makeTabPermanent}
  102. openContextMenu={openContextMenu}
  103. closeContextMenu={closeContextMenu}
  104. />
  105. ))}
  106. </div>
  107. <div className="editor-tabs-labs-icon">
  108. <SplitTestBadge
  109. splitTestName="editor-tabs"
  110. displayOnVariants={['enabled']}
  111. />
  112. </div>
  113. <TabsContextMenu />
  114. </div>
  115. )
  116. }