diff --git a/src/app/workspace/screen/tab2.less b/src/app/workspace/screen/tab2.less index bbfbc6f4b..c26680b7f 100644 --- a/src/app/workspace/screen/tab2.less +++ b/src/app/workspace/screen/tab2.less @@ -1,24 +1,100 @@ .screen-tab { - position: absolute; - border: 1px solid #ccc; - background-color: var(--app-bg-color); - cursor: pointer; - user-select: none; - width: 100px; - height: 46px; - color: var(--app-text-primary-color); - border-radius: 0; - box-sizing: border-box; + font: var(--base-font); + font-size: var(--screentabs-font-size); + line-height: var(--screentabs-line-height); + border-top: 2px solid transparent; + background: var(--app-bg-color); + .background { + // This applies a transparency mask to the background color, as set above, so that it will blend with whatever the theme's background color is. + z-index: 1; + width: var(--screen-tab-width); + mask-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0) 100%); + } + &.is-active { + opacity: 1; + font-weight: var(--screentabs-selected-font-weight); + border-top: 2px solid var(--tab-color); + } + &.is-archived { + .fa.fa-archive { + margin-right: 4px; + } + } + svg.svg-icon-inner path { + fill: var(--tab-color); + } + .tabicon i { + color: var(--tab-color); + } + &.color-green, + &.color-default { + --tab-color: var(--tab-green); + } + &.color-orange { + --tab-color: var(--tab-orange); + } + &.color-red { + --tab-color: var(--tab-red); + } + &.color-yellow { + --tab-color: var(--tab-yellow); + } + &.color-blue { + --tab-color: var(--tab-blue); + } + &.color-mint { + --tab-color: var(--tab-mint); + } + &.color-cyan { + --tab-color: var(--tab-cyan); + } + &.color-white { + --tab-color: var(--tab-white); + } + &.color-violet { + --tab-color: var(--tab-violet); + } + &.color-pink { + --tab-color: var(--tab-pink); + } + .screen-tab-inner { + display: flex; + flex-direction: row; + position: absolute; + z-index: 2; + min-width: var(--screen-tab-width); + max-width: var(--screen-tab-width); + align-items: center; + cursor: pointer; + padding: 8px 8px 4px 8px; // extra 4px of tab padding to account for horizontal scrollbar (to make tab text look centered) + .front-icon { + .positional-icon-visible; + } - &.active-screen-tab { - // background-color: @activeTabColor; - // border-bottom: 2px solid @buttonBackgroundColor; + .tab-name { + flex-grow: 1; + } + + // Only one of these will be visible at a time + .end-icons { + // This adjusts the position of the icon to account for the default 8px margin on the parent. We want the positional calculations for this icon to assume it is flush with the edge of the screen tab. + margin: 0 -5px 0 0; + line-height: normal; + .tab-index { + font-size: 12.5px; + } + } + } + .vertical-line { + border-left: 1px solid var(--app-border-color); + margin: 10px 0 8px 0; + } + &:not(:hover) .status-indicator { + .status-indicator-visible; + } + &:hover { + .actions { + .positional-icon-visible; + } } } - -.screen-tab-inner { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); -} diff --git a/src/app/workspace/screen/tab2.tsx b/src/app/workspace/screen/tab2.tsx index 391f28501..96f1e4866 100644 --- a/src/app/workspace/screen/tab2.tsx +++ b/src/app/workspace/screen/tab2.tsx @@ -1,27 +1,71 @@ import React, { useRef } from "react"; +import cn from "classnames"; +import { ActionsIcon, StatusIndicator, CenteredIcon } from "@/common/icons/icons"; +import { TabIcon } from "@/elements/tabicon"; +import { GlobalModel, Screen } from "@/models"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import * as constants from "@/app/appconst"; + import "./tab2.less"; type ScreenTabProps = { - name: string; - onSelect: (tabName: string) => void; - active: boolean; + screen: Screen; + activeScreenId: string; onDragStart: (name: string, ref: React.RefObject) => void; + onSwitchScreen: (screenId: string) => void; }; -const ScreenTab: React.FC = ({ name, onSelect, active, onDragStart }) => { - const ref = useRef(null); +const ScreenTab: React.FC = mobxReact.observer( + ({ screen, activeScreenId, onSwitchScreen, onDragStart }) => { + const ref = useRef(null); - return ( -
onDragStart(name, ref)} - onClick={() => onSelect(name)} - data-screentab-name={name} - > -
{name}
-
- ); -}; + const openScreenSettings = (e: any, screen: Screen): void => { + e.preventDefault(); + e.stopPropagation(); + mobx.action(() => { + GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); + })(); + GlobalModel.modalsModel.pushModal(constants.SCREEN_SETTINGS); + }; + + const archived = screen.archived.get() ? ( + + ) : null; + const statusIndicatorLevel = screen.statusIndicator.get(); + const runningCommands = screen.numRunningCmds.get() > 0; + + return ( +
onDragStart(screen.name.get(), ref)} + onClick={() => onSwitchScreen(screen.screenId)} + data-screentab-name={screen.name.get()} + > +
+
+ + + +
+ {archived} + {screen.name.get()} +
+
+ + openScreenSettings(e, screen)} /> +
+
+
+
+ ); + } +); export { ScreenTab }; diff --git a/src/app/workspace/screen/tabs2.less b/src/app/workspace/screen/tabs2.less index 9ecc2f1f9..bf6db568f 100644 --- a/src/app/workspace/screen/tabs2.less +++ b/src/app/workspace/screen/tabs2.less @@ -1,28 +1,28 @@ .screen-tabs-container { position: relative; height: var(--screentabs-height); -} -.screen-tabs-container-inner { - position: relative; // Needed for absolute positioning of child tabs - white-space: nowrap; - height: 100%; - margin-right: 42px; - overflow: hidden; -} + .screen-tabs-container-inner { + position: relative; // Needed for absolute positioning of child tabs + white-space: nowrap; + height: 100%; + margin-right: 42px; + overflow: hidden; + } -.new-screen-button { - width: 42px; - height: 40px; - background-color: var(--app-bg-color); - cursor: pointer; - position: absolute; - top: 50%; - transform: translateY(-50%); - font-size: 20px; - text-align: center; - line-height: 38px; - user-select: none; + .new-screen { + width: 42px; + height: 40px; + background-color: var(--app-bg-color); + cursor: pointer; + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 20px; + text-align: center; + line-height: 38px; + user-select: none; + } } // This ensures the tab bar does not collide with the floating logo. The floating logo sits above the sidebar when it is not collapsed, so no additional margin is needed in that case. diff --git a/src/app/workspace/screen/tabs2.tsx b/src/app/workspace/screen/tabs2.tsx index 18f9bff6b..2f31abdef 100644 --- a/src/app/workspace/screen/tabs2.tsx +++ b/src/app/workspace/screen/tabs2.tsx @@ -1,8 +1,10 @@ import React, { useState, useCallback, useRef, useEffect } from "react"; -import { computed } from "mobx"; +import { reaction } from "mobx"; import { ScreenTab } from "./tab2"; -import { observer } from "mobx-react"; +import { observer, useLocalObservable } from "mobx-react"; +import { For } from "tsx-control-statements/components"; import { GlobalModel, GlobalCommandRunner, Session, Screen } from "@/models"; +import AddIcon from "@/assets/icons/add.svg"; import "./tabs2.less"; @@ -13,10 +15,9 @@ type ScreenTabsProps = { }; const ScreenTabs: React.FC = observer(({ session }) => { - const [tabs, setTabs] = useState(["Tab1"]); - const [activeTab, setActiveTab] = useState("Tab1"); + const [screens, setScreens] = useState([]); const [tabWidth, setTabWidth] = useState(DEFAULT_TAB_WIDTH); - const [draggedTab, setDraggedTab] = useState(null); + const [_, setDraggedTab] = useState(null); const [dragStartPositions, setDragStartPositions] = useState([]); const tabContainerRef = useRef(null); const addBtnRef = useRef(null); @@ -27,6 +28,40 @@ const ScreenTabs: React.FC = observer(({ session }) => { let draggedRemoved: boolean; let shrunk: boolean; + const store = useLocalObservable(() => ({ + get activeScreenId() { + return session?.activeScreenId.get(); + }, + get screens() { + let activeScreenId = store.activeScreenId; + if (!activeScreenId) { + return []; + } + + let screens = GlobalModel.getSessionScreens(session.sessionId); + let filteredScreens = screens.filter( + (screen) => !screen.archived.get() || activeScreenId === screen.screenId + ); + + filteredScreens.sort((a, b) => a.screenIdx.get() - b.screenIdx.get()); + return filteredScreens; + }, + })); + + useEffect(() => { + // Update tabs when screens change + const dispose = reaction( + () => store.screens, + (screens) => { + setScreens(screens); + } + ); + // Clean up + return () => { + if (dispose) dispose(); + }; + }, [screens.length]); + const getActiveScreenId = (): string | null => { if (session) { return session.activeScreenId.get(); @@ -34,26 +69,6 @@ const ScreenTabs: React.FC = observer(({ session }) => { return null; }; - const getScreens = computed((): Screen[] => { - let activeScreenId = getActiveScreenId(); - if (!activeScreenId) { - return []; - } - - let screens = GlobalModel.getSessionScreens(session.sessionId); - let showingScreens = []; - - for (const screen of screens) { - if (!screen.archived.get() || activeScreenId === screen.screenId) { - showingScreens.push(screen); - } - } - - showingScreens.sort((a, b) => a.screenIdx.get() - b.screenIdx.get()); - - return showingScreens; - }); - const updateTabPositions = useCallback(() => { if (tabContainerRef.current) { const tabElements = Array.from(tabContainerRef.current.querySelectorAll(".screen-tab")); @@ -67,16 +82,16 @@ const ScreenTabs: React.FC = observer(({ session }) => { setDragStartPositions(newStartPositions); } - }, [tabs]); + }, [screens]); useEffect(() => { updateTabPositions(); - }, [tabs, updateTabPositions]); + }, [screens, updateTabPositions]); const resizeTabs = useCallback(() => { if (tabContainerRef.current) { const containerWidth = tabContainerRef.current.getBoundingClientRect().width; - const numberOfTabs = tabs.length; + const numberOfTabs = screens.length; const totalDefaultTabWidth = numberOfTabs * DEFAULT_TAB_WIDTH; if (totalDefaultTabWidth > containerWidth) { @@ -84,9 +99,9 @@ const ScreenTabs: React.FC = observer(({ session }) => { shrunk = true; const newTabWidth = containerWidth / numberOfTabs; setTabWidth(newTabWidth); - tabs.forEach((tab, index) => { + screens.forEach((screen, index) => { const tabElement = tabContainerRef.current.querySelector( - `[data-screentab-name="${tab}"]` + `[data-screentab-name="${screen.name.get()}"]` ) as HTMLElement; tabElement.style.width = `${newTabWidth}px`; tabElement.style.left = `${index * newTabWidth}px`; @@ -95,9 +110,9 @@ const ScreenTabs: React.FC = observer(({ session }) => { // Case where tabs were previously shrunk or there is enough space for default width tabs shrunk = false; setTabWidth(DEFAULT_TAB_WIDTH); - tabs.forEach((tab, index) => { + screens.forEach((screen, index) => { const tabElement = tabContainerRef.current.querySelector( - `[data-screentab-name="${tab}"]` + `[data-screentab-name="${screen.name.get()}"]` ) as HTMLElement; tabElement.style.width = `${DEFAULT_TAB_WIDTH}px`; tabElement.style.left = `${index * DEFAULT_TAB_WIDTH}px`; @@ -120,7 +135,7 @@ const ScreenTabs: React.FC = observer(({ session }) => { } updateTabPositions(); } - }, [tabs.length, updateTabPositions]); + }, [screens.length, updateTabPositions]); // Resize tabs when the number of tabs or the window size changes useEffect(() => { @@ -137,9 +152,9 @@ const ScreenTabs: React.FC = observer(({ session }) => { }, [mainSidebarWidth, rightSidebarWidth]); const onDragStart = useCallback( - (name: string, ref: React.RefObject) => { - setDraggedTab(name); - let tabIndex = tabs.indexOf(name); + (screenId: string, ref: React.RefObject) => { + setDraggedTab(screenId); + let tabIndex = screens.findIndex((screen) => screen.screenId === screenId); const tabStartX = dragStartPositions[tabIndex]; // Starting X position of the tab const containerWidth = tabContainerRef.current.getBoundingClientRect().width; @@ -154,7 +169,7 @@ const ScreenTabs: React.FC = observer(({ session }) => { // Constrain movement within the container bounds if (tabContainerRef.current) { - const numberOfTabs = tabs.length; + const numberOfTabs = screens.length; const totalDefaultTabWidth = numberOfTabs * DEFAULT_TAB_WIDTH; const containerRect = tabContainerRef.current.getBoundingClientRect(); let containerRectWidth = containerRect.width; @@ -188,7 +203,7 @@ const ScreenTabs: React.FC = observer(({ session }) => { if (dragDirection === "+") { // Dragging to the right - for (let i = tabIndex + 1; i < tabs.length; i++) { + for (let i = tabIndex + 1; i < screens.length; i++) { const otherTabStart = dragStartPositions[i]; if (currentX + tabWidth > otherTabStart + tabWidth / 2) { newTabIndex = i; @@ -206,11 +221,11 @@ const ScreenTabs: React.FC = observer(({ session }) => { // Rearrange the tabs temporarily if (newTabIndex !== tabIndex) { - const tempTabs = Array.from(tabs); + const tempTabs = Array.from(screens); // Remove the dragged tab if not already done if (!draggedRemoved) { - tabs.splice(tabIndex, 1); + screens.splice(tabIndex, 1); draggedRemoved = true; } @@ -253,10 +268,10 @@ const ScreenTabs: React.FC = observer(({ session }) => { }); // Update the final position of the dragged tab - const draggedTab = tabs[tabIndex]; + const draggedTab = screens[tabIndex]; const finalLeftPosition = tabIndex * tabWidth; const draggedTabElement = tabContainerRef.current.querySelector( - `[data-screentab-name="${draggedTab}"]` + `[data-screentab-name="${draggedTab.name.get()}"]` ) as HTMLElement; if (draggedTabElement) { draggedTabElement.style.left = `${finalLeftPosition}px`; @@ -270,34 +285,48 @@ const ScreenTabs: React.FC = observer(({ session }) => { document.addEventListener("mouseup", handleMouseUp); } }, - [tabs, dragStartPositions] + [screens, dragStartPositions] ); - const selectTab = (tabName: string) => { - setActiveTab(tabName); + const onSwitchScreen = (screenId: string) => { + if (session == null) { + return; + } + if (session.activeScreenId.get() == screenId) { + return; + } + let screen = session.getScreenById(screenId); + if (screen == null) { + return; + } + GlobalCommandRunner.switchScreen(screenId); }; - const addTab = () => { - const newTabName = `Tab${tabs.length + 1}`; - setTabs([...tabs, newTabName]); - setActiveTab(newTabName); + const handleNewScreen = () => { + GlobalCommandRunner.createNewScreen(); }; + if (session == null) { + return null; + } + const screen: Screen | null = null; + const activeScreenId = getActiveScreenId(); + return (
- {tabs.map((tab) => ( + - ))} +
-
- + +
+
);