tabs-container.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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. } = useTabsContext()
  22. const { openEntity } = useFileTreeOpenContext()
  23. const [hovered, setHovered] = useState<boolean>(false)
  24. const openContextMenu = useCallback(
  25. (coords: { top: number; left: number }, tabId: string) => {
  26. setContextMenuTarget({ ...coords, tabId })
  27. },
  28. [setContextMenuTarget]
  29. )
  30. const closeContextMenu = useCallback(() => {
  31. setContextMenuTarget(null)
  32. }, [setContextMenuTarget])
  33. const throttledOnDragOver = useMemo(
  34. () =>
  35. throttle(() => {
  36. setHovered(true)
  37. }, 50),
  38. []
  39. )
  40. const onDragOver = useCallback(
  41. (e: React.DragEvent) => {
  42. e.preventDefault()
  43. throttledOnDragOver()
  44. },
  45. [throttledOnDragOver]
  46. )
  47. const onDrop = useCallback(
  48. (e: React.DragEvent) => {
  49. throttledOnDragOver.cancel()
  50. e.stopPropagation()
  51. e.preventDefault()
  52. setHovered(false)
  53. const draggedTabId = e.dataTransfer.getData(TAB_TRANSFER_TYPE)
  54. if (!draggedTabId) {
  55. debugConsole.warn('No dragged tab id found in dataTransfer')
  56. return
  57. }
  58. const targetTabId = tabs[tabs.length - 1]?.id
  59. if (!targetTabId) {
  60. debugConsole.warn('No target tab id found for drop')
  61. return
  62. }
  63. moveTab(draggedTabId, targetTabId, 'right')
  64. },
  65. [tabs, moveTab, throttledOnDragOver]
  66. )
  67. const onDragLeave = useCallback(() => {
  68. throttledOnDragOver.cancel()
  69. setHovered(false)
  70. }, [throttledOnDragOver])
  71. const onWheel = useCallback((e: React.WheelEvent) => {
  72. if (e.deltaY !== 0 && e.deltaX === 0) {
  73. // if this is a purely vertical scroll, convert it to a horizontal scroll
  74. // instead
  75. e.currentTarget.scrollLeft += e.deltaY
  76. }
  77. }, [])
  78. return (
  79. <div className="editor-tabs-container">
  80. <div
  81. className={classNames('editor-tabs-row', {
  82. 'editor-tabs-row-hovered': hovered,
  83. })}
  84. role="tablist"
  85. onDragOver={onDragOver}
  86. onDrop={onDrop}
  87. onDragLeave={onDragLeave}
  88. onWheel={onWheel}
  89. tabIndex={-1}
  90. >
  91. {tabs.map(tab => (
  92. <Tab
  93. key={tab.id}
  94. tab={tab}
  95. openTab={openTab}
  96. closeTab={closeTab}
  97. isSelected={openEntity?.entity._id === tab.id}
  98. onTabDrop={moveTab}
  99. makeTabPermanent={makeTabPermanent}
  100. openContextMenu={openContextMenu}
  101. closeContextMenu={closeContextMenu}
  102. />
  103. ))}
  104. </div>
  105. <div className="editor-tabs-labs-icon">
  106. <SplitTestBadge
  107. splitTestName="editor-tabs"
  108. displayOnVariants={['enabled']}
  109. />
  110. </div>
  111. <TabsContextMenu />
  112. </div>
  113. )
  114. }