diff --git a/frontend/app/app.less b/frontend/app/app.less index 37e2bfa1..67560f56 100644 --- a/frontend/app/app.less +++ b/frontend/app/app.less @@ -51,13 +51,6 @@ body { height: 100%; } -.titlebar { - height: 35px; - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; - -webkit-app-region: drag; -} - .error-boundary { color: var(--error-color); } diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 9f0fdc70..5906eec1 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -192,7 +192,6 @@ const AppInner = () => { if (client == null || windowData == null) { return (
-
invalid configuration, client or window was not loaded
); @@ -243,7 +242,6 @@ const AppInner = () => { return (
-
diff --git a/frontend/app/element/windowdrag.less b/frontend/app/element/windowdrag.less new file mode 100644 index 00000000..4bdebe0e --- /dev/null +++ b/frontend/app/element/windowdrag.less @@ -0,0 +1,7 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +.window-drag { + -webkit-app-region: drag; + z-index: 100; +} diff --git a/frontend/app/element/windowdrag.tsx b/frontend/app/element/windowdrag.tsx new file mode 100644 index 00000000..3c356d06 --- /dev/null +++ b/frontend/app/element/windowdrag.tsx @@ -0,0 +1,22 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { clsx } from "clsx"; +import React, { forwardRef } from "react"; + +import "./windowdrag.less"; + +interface WindowDragProps { + className?: string; + children?: React.ReactNode; +} + +const WindowDrag = forwardRef(({ children, className }, ref) => { + return ( +
+ {children} +
+ ); +}); + +export { WindowDrag }; diff --git a/frontend/app/tab/tab.less b/frontend/app/tab/tab.less index 090c5444..2647fe8f 100644 --- a/frontend/app/tab/tab.less +++ b/frontend/app/tab/tab.less @@ -53,6 +53,10 @@ background-color: var(--border-color); } + .vertical-line.first { + left: 0; + } + .close { visibility: hidden; position: absolute; diff --git a/frontend/app/tab/tab.tsx b/frontend/app/tab/tab.tsx index dd7db302..513494b0 100644 --- a/frontend/app/tab/tab.tsx +++ b/frontend/app/tab/tab.tsx @@ -1,5 +1,7 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import { Button } from "@/element/button"; -import { ContextMenuModel } from "@/store/contextmenu"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; import { clsx } from "clsx"; @@ -10,16 +12,17 @@ import "./tab.less"; interface TabProps { id: string; active: boolean; + isFirst: boolean; isBeforeActive: boolean; isDragging: boolean; onSelect: () => void; - onClose: (event: React.MouseEvent) => void; + onClose: (event: React.MouseEvent) => void; onDragStart: (event: React.MouseEvent) => void; onLoaded: () => void; } const Tab = forwardRef( - ({ id, active, isBeforeActive, isDragging, onLoaded, onSelect, onClose, onDragStart }, ref) => { + ({ id, active, isFirst, isBeforeActive, isDragging, onLoaded, onSelect, onClose, onDragStart }, ref) => { const [tabData, tabLoading] = WOS.useWaveObjectValue(WOS.makeORef("tab", id)); const [originalName, setOriginalName] = useState(""); const [isEditable, setIsEditable] = useState(false); @@ -42,10 +45,8 @@ const Tab = forwardRef( }; }, []); - const handleDoubleClick = (event?: React.MouseEvent) => { - if (event != null) { - event.stopPropagation(); - } + const handleDoubleClick = (event) => { + event.stopPropagation(); setIsEditable(true); editableTimeoutRef.current = setTimeout(() => { if (editableRef.current) { @@ -105,35 +106,15 @@ const Tab = forwardRef( event.stopPropagation(); }; - function handleContextMenu(e: React.MouseEvent) { - let menu: ContextMenuItem[] = []; - menu.push({ - label: "Edit Name", - click: () => { - handleDoubleClick(null); - }, - }); - menu.push({ - type: "separator", - }); - menu.push({ - label: "Close", - click: () => { - onClose(e); - }, - }); - ContextMenuModel.showContextMenu(menu, e); - } - return (
+ {isFirst &&
}
{ const [dragStartPositions, setDragStartPositions] = useState([]); const [draggingTab, setDraggingTab] = useState(); const [tabsLoaded, setTabsLoaded] = useState({}); - const [scrollable, setScrollable] = useState(false); - const [tabWidth, setTabWidth] = useState(TAB_DEFAULT_WIDTH); + // const [scrollable, setScrollable] = useState(false); + // const [tabWidth, setTabWidth] = useState(TAB_DEFAULT_WIDTH); const tabBarRef = useRef(null); const tabsWrapperRef = useRef(null); @@ -61,6 +62,9 @@ const TabBar = ({ workspace }: TabBarProps) => { dragged: false, }); const osInstanceRef = useRef(null); + const draggerRightRef = useRef(null); + const tabWidthRef = useRef(TAB_DEFAULT_WIDTH); + const scrollableRef = useRef(false); const windowData = useAtomValue(atoms.waveWindow); const { activetabid } = windowData; @@ -105,18 +109,21 @@ const TabBar = ({ workspace }: TabBarProps) => { setDragStartPositions(newStartPositions); }, []); - const debouncedSetTabWidth = debounce((width) => setTabWidth(width), 100); - const debouncedSetScrollable = debounce((scrollable) => setScrollable(scrollable), 100); + // const debouncedSetTabWidth = debounce((width) => setTabWidth(width), 100); + // const debouncedSetScrollable = debounce((scrollable) => setScrollable(scrollable), 100); const debouncedUpdateTabPositions = debounce(() => updateTabPositions(), 100); const handleResizeTabs = useCallback(() => { const tabBar = tabBarRef.current; if (tabBar === null) return; - const tabBarWidth = tabBar.getBoundingClientRect().width; + const tabBarRect = tabBar.getBoundingClientRect(); + const tabBarWidth = tabBarRect.width; const numberOfTabs = tabIds.length; const totalDefaultTabWidth = numberOfTabs * TAB_DEFAULT_WIDTH; const minTotalTabWidth = numberOfTabs * TAB_MIN_WIDTH; + const tabWidth = tabWidthRef.current; + const scrollable = scrollableRef.current; let newTabWidth = tabWidth; let newScrollable = scrollable; @@ -144,11 +151,11 @@ const TabBar = ({ workspace }: TabBarProps) => { // Update the state with the new tab width if it has changed if (newTabWidth !== tabWidth) { - debouncedSetTabWidth(newTabWidth); + tabWidthRef.current = newTabWidth; } // Update the state with the new scrollable state if it has changed if (newScrollable !== scrollable) { - debouncedSetScrollable(newScrollable); + scrollableRef.current = newScrollable; } // Initialize/destroy overlay scrollbars if (newScrollable) { @@ -159,21 +166,29 @@ const TabBar = ({ workspace }: TabBarProps) => { } } - // Update the position of the Add Tab button if needed + // Update Add Tab button position if needed const addButton = addBtnRef.current; const lastTabRef = tabRefs.current[tabRefs.current.length - 1]; if (addButton && lastTabRef && lastTabRef.current) { const lastTabRect = lastTabRef.current.getBoundingClientRect(); addButton.style.position = "absolute"; if (newScrollable) { - addButton.style.transform = `translateX(${document.documentElement.clientWidth - addButton.offsetWidth}px) translateY(-50%)`; + addButton.style.transform = `translateX(${tabBarRect.left + tabBarWidth + 1}px)`; } else { - addButton.style.transform = `translateX(${lastTabRect.right + 1}px) translateY(-50%)`; + addButton.style.transform = `translateX(${lastTabRect.right + 1}px)`; } } + // Update dragger right position if needed + const draggerRight = draggerRightRef.current; + if (draggerRight && addButton) { + const addButtonRect = addButton.getBoundingClientRect(); + const targetPos = addButtonRect.left + addButtonRect.width; + draggerRight.style.transform = `translateX(${targetPos}px)`; + draggerRight.style.width = `${document.documentElement.offsetWidth - targetPos}px`; + } debouncedUpdateTabPositions(); - }, [tabIds, tabWidth, scrollable]); + }, [tabIds]); useEffect(() => { window.addEventListener("resize", () => handleResizeTabs()); @@ -219,6 +234,7 @@ const TabBar = ({ workspace }: TabBarProps) => { const getNewTabIndex = (currentX: number, tabIndex: number, dragDirection: string) => { let newTabIndex = tabIndex; + const tabWidth = tabWidthRef.current; if (dragDirection === "+") { // Dragging to the right for (let i = tabIndex + 1; i < tabIds.length; i++) { @@ -250,21 +266,26 @@ const TabBar = ({ workspace }: TabBarProps) => { } let currentX = event.clientX - initialOffsetX - totalScrollOffset; let tabBarRectWidth = tabBarRef.current.getBoundingClientRect().width; + // for macos, it's offset to make space for the window buttons + const tabBarRectLeftOffset = tabBarRef.current.getBoundingClientRect().left; + const incrementDecrement = tabBarRectLeftOffset * 0.05; const dragDirection = getDragDirection(currentX); + const scrollable = scrollableRef.current; + const tabWidth = tabWidthRef.current; // Scroll the tab bar if the dragged tab overflows the container bounds if (scrollable) { const { viewport } = osInstanceRef.current.elements(); const currentScrollLeft = viewport.scrollLeft; - if (event.clientX <= 0) { - viewport.scrollLeft = Math.max(0, currentScrollLeft - 5); // Scroll left + if (event.clientX <= tabBarRectLeftOffset) { + viewport.scrollLeft = Math.max(0, currentScrollLeft - incrementDecrement); // Scroll left if (viewport.scrollLeft !== currentScrollLeft) { // Only adjust if the scroll actually changed draggingTabDataRef.current.totalScrollOffset += currentScrollLeft - viewport.scrollLeft; } - } else if (event.clientX >= tabBarRectWidth) { - viewport.scrollLeft = Math.min(viewport.scrollWidth, currentScrollLeft + 5); // Scroll right + } else if (event.clientX >= tabBarRectWidth + tabBarRectLeftOffset) { + viewport.scrollLeft = Math.min(viewport.scrollWidth, currentScrollLeft + incrementDecrement); // Scroll right if (viewport.scrollLeft !== currentScrollLeft) { // Only adjust if the scroll actually changed draggingTabDataRef.current.totalScrollOffset -= viewport.scrollLeft - currentScrollLeft; @@ -343,6 +364,7 @@ const TabBar = ({ workspace }: TabBarProps) => { // Update the final position of the dragged tab const draggingTab = tabIds[tabIndex]; + const tabWidth = tabWidthRef.current; const finalLeftPosition = tabIndex * tabWidth; const ref = tabRefs.current.find((ref) => ref.current.dataset.tabId === draggingTab); if (ref.current) { @@ -418,14 +440,14 @@ const TabBar = ({ workspace }: TabBarProps) => { services.ObjectService.AddTabToWorkspace(newTabName, true); scrollToNewTabTimeoutIdRef.current = setTimeout(() => { - if (scrollable) { + if (scrollableRef.current) { const { viewport } = osInstanceRef.current.elements(); - viewport.scrollLeft = tabIds.length * tabWidth; + viewport.scrollLeft = tabIds.length * tabWidthRef.current; } }, 30); }; - const handleCloseTab = (event: React.MouseEvent, tabId: string) => { + const handleCloseTab = (event: React.MouseEvent, tabId: string) => { event.stopPropagation(); services.WindowService.CloseTab(tabId); deleteLayoutStateAtomForTab(tabId); @@ -445,10 +467,11 @@ const TabBar = ({ workspace }: TabBarProps) => { return tabIds.indexOf(tabId) === tabIds.indexOf(activetabid) - 1; }; - const tabsWrapperWidth = tabIds.length * tabWidth; + const tabsWrapperWidth = tabIds.length * tabWidthRef.current; return (
+
{tabIds.map((tabId, index) => ( @@ -456,6 +479,7 @@ const TabBar = ({ workspace }: TabBarProps) => { key={tabId} ref={tabRefs.current[index]} id={tabId} + isFirst={index === 0} onSelect={() => handleSelectTab(tabId)} active={activetabid === tabId} onDragStart={(event) => handleDragStart(event, tabId, tabRefs.current[index])} @@ -470,6 +494,7 @@ const TabBar = ({ workspace }: TabBarProps) => {
+
); }; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index eb1e6214..5079dd4f 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -352,4 +352,4 @@ declare global { } -export {} +export {} \ No newline at end of file