diff --git a/src/ScriptEditor/ui/Tab.tsx b/src/ScriptEditor/ui/Tab.tsx new file mode 100644 index 000000000..687b65964 --- /dev/null +++ b/src/ScriptEditor/ui/Tab.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useRef } from "react"; +import { DraggableProvided } from "react-beautiful-dnd"; + +import Button from "@mui/material/Button"; +import Tooltip from "@mui/material/Tooltip"; + +import SyncIcon from "@mui/icons-material/Sync"; +import CloseIcon from "@mui/icons-material/Close"; + +import { Settings } from "../../Settings/Settings"; + +interface IProps { + provided: DraggableProvided; + title: string; + isActive: boolean; + isExternal: boolean; + + onClick: () => void; + onClose: () => void; + onUpdate: () => void; +} + +const tabMargin = 5; +const tabIconWidth = 25; +const tabIconHeight = 38.5; + +export function Tab({ provided, title, isActive, isExternal, onClick, onClose, onUpdate }: IProps) { + const colorProps = isActive + ? { + background: Settings.theme.button, + borderColor: Settings.theme.button, + color: Settings.theme.primary, + } + : { + background: Settings.theme.backgroundsecondary, + borderColor: Settings.theme.backgroundsecondary, + color: Settings.theme.secondary, + }; + + if (isExternal) { + colorProps.color = Settings.theme.info; + } + const iconButtonStyle = { + maxWidth: tabIconWidth, + minWidth: tabIconWidth, + minHeight: tabIconHeight, + maxHeight: tabIconHeight, + ...colorProps, + }; + + const tabRef = useRef(null); + + useEffect(() => { + if (tabRef.current && isActive) { + tabRef.current?.scrollIntoView(); + } + }, [isActive]); + + return ( +
{ + tabRef.current = element; + provided.innerRef(element); + }} + {...provided.draggableProps} + {...provided.dragHandleProps} + style={{ + ...provided.draggableProps.style, + marginRight: tabMargin, + flexShrink: 0, + border: "1px solid " + Settings.theme.well, + }} + > + + + + + + + +
+ ); +} diff --git a/src/ScriptEditor/ui/Tabs.tsx b/src/ScriptEditor/ui/Tabs.tsx index 1b25f861d..3454d9872 100644 --- a/src/ScriptEditor/ui/Tabs.tsx +++ b/src/ScriptEditor/ui/Tabs.tsx @@ -1,20 +1,24 @@ import React, { useState } from "react"; -import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; +import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd"; + +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import IconButton from "@mui/material/IconButton"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; -import { Box, Button, TextField, Tooltip } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import SearchIcon from "@mui/icons-material/Search"; -import SyncIcon from "@mui/icons-material/Sync"; -import { useRerender } from "../../ui/React/hooks"; +import { useBoolean, useRerender } from "../../ui/React/hooks"; import { Settings } from "../../Settings/Settings"; import { dirty, reorder } from "./utils"; import { OpenScript } from "./OpenScript"; +import { Tab } from "./Tab"; const tabsMaxWidth = 1640; -const tabMargin = 5; -const tabIconWidth = 25; +const searchWidth = 180; interface IProps { scripts: OpenScript[]; @@ -27,160 +31,123 @@ interface IProps { export function Tabs({ scripts, currentScript, onTabClick, onTabClose, onTabUpdate }: IProps) { const [filter, setFilter] = useState(""); + const [isSearchTooltipOpen, { on: openSearchTooltip, off: closeSearchTooltip }] = useBoolean(false); const [searchExpanded, setSearchExpanded] = useState(false); const rerender = useRerender(); - function onDragEnd(result: any): void { + const filteredScripts = Object.values(scripts) + .map((script, originalIndex) => ({ script, originalIndex })) + .filter(({ script }) => script.hostname.includes(filter) || script.path.includes(filter)); + + function onDragEnd(result: DropResult): void { // Dropped outside of the list if (!result.destination) return; - reorder(scripts, result.source.index, result.destination.index); + reorder( + scripts, + filteredScripts[result.source.index].originalIndex, + filteredScripts[result.destination.index].originalIndex, + ); rerender(); } - const filteredOpenScripts = Object.values(scripts).filter( - (script) => script.hostname.includes(filter) || script.path.includes(filter), - ); - function handleFilterChange(event: React.ChangeEvent): void { setFilter(event.target.value); } - function handleExpandSearch(): void { + function toggleSearch(): void { setFilter(""); setSearchExpanded(!searchExpanded); + closeSearchTooltip(); + } + + function handleScroll(e: React.WheelEvent): void { + e.currentTarget.scrollLeft += e.deltaY; } - const tabMaxWidth = filteredOpenScripts.length ? tabsMaxWidth / filteredOpenScripts.length - tabMargin : 0; - const tabTextWidth = tabMaxWidth - tabIconWidth * 2; return ( - - - {(provided, snapshot) => ( - - - {searchExpanded ? ( - , - spellCheck: false, - endAdornment: , - // TODO: reapply - // sx: { minWidth: 200 }, - }} - /> - ) : ( - - )} - - {filteredOpenScripts.map(({ path: fileName, hostname }, index) => { - const editingCurrentScript = - currentScript?.path === filteredOpenScripts[index].path && - currentScript.hostname === filteredOpenScripts[index].hostname; - const externalScript = hostname !== "home"; - const colorProps = editingCurrentScript - ? { - background: Settings.theme.button, - borderColor: Settings.theme.button, - color: Settings.theme.primary, - } - : { - background: Settings.theme.backgroundsecondary, - borderColor: Settings.theme.backgroundsecondary, - color: Settings.theme.secondary, - }; + + + + {searchExpanded ? ( + , + spellCheck: false, + endAdornment: ( + + + + ), + }} + /> + ) : ( + + )} + + + + + {(provided, snapshot) => ( + + {filteredScripts.map(({ script, originalIndex }, index) => { + const { path: fileName, hostname } = script; + const isActive = currentScript?.path === script.path && currentScript.hostname === script.hostname; - if (externalScript) { - colorProps.color = Settings.theme.info; - } - const iconButtonStyle = { - maxWidth: `${tabIconWidth}px`, - minWidth: `${tabIconWidth}px`, - minHeight: "38.5px", - maxHeight: "38.5px", - ...colorProps, - }; + const title = `${hostname}:~${fileName.startsWith("/") ? "" : "/"}${fileName} ${dirty(scripts, index)}`; - const scriptTabText = `${hostname}:~${fileName.startsWith("/") ? "" : "/"}${fileName} ${dirty( - scripts, - index, - )}`; - - return ( - - {(provided) => ( -
- - - - - - - -
- )} -
- ); - })} - {provided.placeholder} -
- )} -
-
+ return ( + + {(provided) => ( + onTabClick(originalIndex)} + onClose={() => onTabClose(originalIndex)} + onUpdate={() => onTabUpdate(originalIndex)} + /> + )} + + ); + })} + {provided.placeholder} +
+ )} +
+
+ ); }