import { EditorFileTab, TAB_TRANSFER_TYPE, } from '@/features/ide-react/context/tabs-context' import FileTreeIcon from '@/features/file-tree/components/file-tree-icon' import MaterialIcon from '@/shared/components/material-icon' import { debugConsole } from '@/utils/debugging' import classNames from 'classnames' import { throttle } from 'lodash' import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react' import { useTranslation } from 'react-i18next' import { TAB_USER_EDIT_EVENT, tabsEvents, } from '@/features/source-editor/extensions/tabs-listener' type TabProps = { tab: EditorFileTab openTab: (id: string) => void closeTab: (id: string) => void canCloseTab: boolean makeTabPermanent: (id: string) => void openContextMenu: ( coords: { top: number; left: number }, tabId: string ) => void closeContextMenu: () => void isSelected: boolean onTabDrop: ( sourceTabId: string, targetTabId: string, position: 'left' | 'right' ) => void } function getSideOfTargetFromEvent( element: Element, clientX: number ): 'left' | 'right' { const rect = element.getBoundingClientRect() if (rect.left + rect.width / 2 < clientX) { return 'right' } else { return 'left' } } export const Tab = memo(function Tab({ tab, openTab, closeTab, canCloseTab, makeTabPermanent, openContextMenu, closeContextMenu, isSelected, onTabDrop, }: TabProps) { const { t } = useTranslation() const tabRef = useRef(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' }, [tab] ) const throttledOnDragOver = useMemo( () => throttle((element: Element, clientX: number) => { setDropTargetPosition(getSideOfTargetFromEvent(element, clientX)) }, 50), [] ) const onDragOver = useCallback( (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() e.dataTransfer.dropEffect = 'move' throttledOnDragOver(e.currentTarget, e.clientX) }, [throttledOnDragOver] ) const onDragLeave = useCallback( (e: React.DragEvent) => { e.stopPropagation() if (e.currentTarget.contains(e.relatedTarget as Node | null)) { return } 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') return } onTabDrop( draggedTabId, tab.id, getSideOfTargetFromEvent(e.currentTarget, e.clientX) ) }, [onTabDrop, tab, throttledOnDragOver] ) const onDoubleClick = useCallback(() => { makeTabPermanent(tab.id) }, [makeTabPermanent, tab]) const onClick = useCallback( (e: React.MouseEvent) => { if (e.button === 0) { openTab(tab.id) } }, [openTab, tab] ) const onKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.code === 'Enter') { openTab(tab.id) } }, [openTab, tab] ) const onCloseClick = useCallback( (e: React.MouseEvent) => { e.stopPropagation() closeTab(tab.id) }, [closeTab, tab] ) const onMouseUp = useCallback( (e: React.MouseEvent) => { if (e.button === 1) { e.stopPropagation() closeTab(tab.id) } }, [closeTab, tab] ) const onContextMenu = useCallback( (e: React.MouseEvent) => { if (e.shiftKey) { closeContextMenu() return } e.preventDefault() openContextMenu({ top: e.pageY, left: e.pageX }, tab.id) }, [openContextMenu, closeContextMenu, tab] ) useLayoutEffect(() => { if (isSelected && tabRef.current) { tabRef.current.scrollIntoView({ block: 'nearest', inline: 'nearest', }) } }, [isSelected]) useEffect(() => { if (isSelected && tab.lifetime === 'temporary') { const handler = () => { makeTabPermanent(tab.id) } tabsEvents.addEventListener(TAB_USER_EDIT_EVENT, handler) return () => { tabsEvents.removeEventListener(TAB_USER_EDIT_EVENT, handler) } } }, [isSelected, makeTabPermanent, tab]) return (
‎{tab.displayPath}
) })