mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-08 08:39:03 +02:00
Merge pull request #32330 from overleaf/mj-tabs-survey
[web] Tweaks for editor tabs GitOrigin-RevId: fed9a500b871fa68a158c2e7ab42030117775161
This commit is contained in:
committed by
Copybot
parent
01f7bba166
commit
6b01183bba
@@ -20,6 +20,8 @@ export type EditorFileTab = {
|
||||
lifetime: Lifetime
|
||||
}
|
||||
|
||||
export const TAB_TRANSFER_TYPE = 'text/x.tab-id'
|
||||
|
||||
const TabsContext = React.createContext<
|
||||
| {
|
||||
tabs: EditorFileTab[]
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { EditorFileTab } from '@/features/ide-react/context/tabs-context'
|
||||
import {
|
||||
EditorFileTab,
|
||||
TAB_TRANSFER_TYPE,
|
||||
} from '@/features/ide-react/context/tabs-context'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import classNames from 'classnames'
|
||||
import { throttle } from 'lodash'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type TabProps = {
|
||||
@@ -31,8 +42,6 @@ function getSideOfTargetFromEvent(
|
||||
}
|
||||
}
|
||||
|
||||
const TAB_TRANSFER_TYPE = 'text/x.tab-id'
|
||||
|
||||
export const Tab = memo(function Tab({
|
||||
tab,
|
||||
openTab,
|
||||
@@ -42,12 +51,14 @@ export const Tab = memo(function Tab({
|
||||
onTabDrop,
|
||||
}: TabProps) {
|
||||
const { t } = useTranslation()
|
||||
const tabRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [dropTargetPosition, setDropTargetPosition] = useState<
|
||||
'left' | 'right' | null
|
||||
>(null)
|
||||
const onDragStart = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
e.stopPropagation()
|
||||
e.dataTransfer.setData(TAB_TRANSFER_TYPE, tab.id)
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
},
|
||||
@@ -65,22 +76,28 @@ export const Tab = memo(function Tab({
|
||||
const onDragOver = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
throttledOnDragOver(e.currentTarget, e.clientX)
|
||||
},
|
||||
[throttledOnDragOver]
|
||||
)
|
||||
|
||||
const onDragLeave = useCallback(() => {
|
||||
throttledOnDragOver.cancel()
|
||||
setDropTargetPosition(null)
|
||||
}, [throttledOnDragOver])
|
||||
const onDragLeave = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
e.stopPropagation()
|
||||
throttledOnDragOver.cancel()
|
||||
setDropTargetPosition(null)
|
||||
},
|
||||
[throttledOnDragOver]
|
||||
)
|
||||
|
||||
const onDrop = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
throttledOnDragOver.cancel()
|
||||
setDropTargetPosition(null)
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const draggedTabId = e.dataTransfer.getData(TAB_TRANSFER_TYPE)
|
||||
if (!draggedTabId) {
|
||||
debugConsole.warn('No dragged tab id found in dataTransfer')
|
||||
@@ -135,6 +152,15 @@ export const Tab = memo(function Tab({
|
||||
[closeTab, tab]
|
||||
)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isSelected && tabRef.current) {
|
||||
tabRef.current.scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'nearest',
|
||||
})
|
||||
}
|
||||
}, [isSelected])
|
||||
|
||||
useEffect(() => {
|
||||
if (isSelected && tab.lifetime === 'temporary') {
|
||||
const handler = () => {
|
||||
@@ -149,6 +175,7 @@ export const Tab = memo(function Tab({
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={tabRef}
|
||||
onDragStart={onDragStart}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
|
||||
+81
-12
@@ -1,25 +1,94 @@
|
||||
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
|
||||
import { useTabsContext } from '@/features/ide-react/context/tabs-context'
|
||||
import {
|
||||
TAB_TRANSFER_TYPE,
|
||||
useTabsContext,
|
||||
} from '@/features/ide-react/context/tabs-context'
|
||||
import { Tab } from './tab'
|
||||
import SplitTestBadge from '@/shared/components/split-test-badge'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { throttle } from 'lodash'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import classNames from 'classnames'
|
||||
|
||||
export const TabsContainer = () => {
|
||||
const { tabs, openTab, closeTab, moveTab, makeTabPermanent } =
|
||||
useTabsContext()
|
||||
const { openEntity } = useFileTreeOpenContext()
|
||||
const [hovered, setHovered] = useState<boolean>(false)
|
||||
|
||||
const throttledOnDragOver = useMemo(
|
||||
() =>
|
||||
throttle(() => {
|
||||
setHovered(true)
|
||||
}, 50),
|
||||
[]
|
||||
)
|
||||
|
||||
const onDragOver = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
throttledOnDragOver()
|
||||
},
|
||||
[throttledOnDragOver]
|
||||
)
|
||||
|
||||
const onDrop = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
throttledOnDragOver.cancel()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
setHovered(false)
|
||||
|
||||
const draggedTabId = e.dataTransfer.getData(TAB_TRANSFER_TYPE)
|
||||
if (!draggedTabId) {
|
||||
debugConsole.warn('No dragged tab id found in dataTransfer')
|
||||
return
|
||||
}
|
||||
const targetTabId = tabs[tabs.length - 1]?.id
|
||||
if (!targetTabId) {
|
||||
debugConsole.warn('No target tab id found for drop')
|
||||
return
|
||||
}
|
||||
moveTab(draggedTabId, targetTabId, 'right')
|
||||
},
|
||||
[tabs, moveTab, throttledOnDragOver]
|
||||
)
|
||||
|
||||
const onDragLeave = useCallback(() => {
|
||||
throttledOnDragOver.cancel()
|
||||
setHovered(false)
|
||||
}, [throttledOnDragOver])
|
||||
|
||||
return (
|
||||
<div className="editor-tabs-container" role="tablist">
|
||||
{tabs.map(tab => (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
openTab={openTab}
|
||||
closeTab={closeTab}
|
||||
isSelected={openEntity?.entity._id === tab.id}
|
||||
onTabDrop={moveTab}
|
||||
makeTabPermanent={makeTabPermanent}
|
||||
<div className="editor-tabs-container">
|
||||
<div
|
||||
className={classNames('editor-tabs-row', {
|
||||
'editor-tabs-row-hovered': hovered,
|
||||
})}
|
||||
role="tablist"
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDrop}
|
||||
onDragLeave={onDragLeave}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{tabs.map(tab => (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
openTab={openTab}
|
||||
closeTab={closeTab}
|
||||
isSelected={openEntity?.entity._id === tab.id}
|
||||
onTabDrop={moveTab}
|
||||
makeTabPermanent={makeTabPermanent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="editor-tabs-labs-icon">
|
||||
<SplitTestBadge
|
||||
splitTestName="editor-tabs"
|
||||
displayOnVariants={['enabled']}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,38 @@
|
||||
.editor-tabs-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid var(--border-divider-themed);
|
||||
margin: 0;
|
||||
align-items: center;
|
||||
gap: var(--spacing-02);
|
||||
flex: 0 0 auto;
|
||||
background: var(--bg-primary-themed);
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--border-divider-themed);
|
||||
|
||||
.editor-tabs-labs-icon {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 var(--spacing-02);
|
||||
|
||||
.material-symbols {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-tabs-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
background: var(--bg-primary-themed);
|
||||
overflow-x: auto;
|
||||
|
||||
&.editor-tabs-row-hovered {
|
||||
.editor-file-tab:last-child {
|
||||
border-right-color: var(--border-primary-themed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-file-tab {
|
||||
|
||||
Reference in New Issue
Block a user