diff --git a/src/app/app.less b/src/app/app.less index 3685a3005..5bf6942f9 100644 --- a/src/app/app.less +++ b/src/app/app.less @@ -250,6 +250,36 @@ a.a-block { } } +.logo-button-container { + width: 105px; + flex-shrink: 0; + position: absolute; + z-index: 25; + top: 7px; + left: 0px; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + -webkit-app-region: drag; + pointer-events: none; + + .logo-button { + width: 25px; + height: 25px; + margin-right: 6px; + cursor: pointer; + user-select: none; + -webkit-app-region: no-drag; + pointer-events: all; + + &:hover { + background-color: #333; + border-radius: 4px; + } + } +} + .copied-indicator { position: absolute; top: 0; @@ -641,42 +671,6 @@ a.a-block { } } -.mainview { - flex-grow: 1; - display: flex; - flex-direction: column; - position: relative; - border-radius: 0 var(--app-border-radius) var(--app-border-radius) 0; - border-bottom: 1px solid var(--app-border-color); - border-right: 1px solid var(--app-border-color); - border-left: 1px solid var(--app-border-color); - background-color: black; - - &.is-hidden { - display: none; - } - - .header { - -webkit-app-region: drag; - padding: 24px 18px; - margin: 0; - display: flex; - justify-content: space-between; - align-items: center; - - .close-div { - -webkit-app-region: no-drag; - font-size: 1.5em; - } - - &.bottom-border { - border-bottom: 1px solid white; - padding-bottom: 20px; - margin-bottom: 20px; - } - } -} - .settings-field { display: flex; flex-direction: row; diff --git a/src/app/app.tsx b/src/app/app.tsx index d6fca4502..7d9229dd7 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -8,7 +8,7 @@ import { boundMethod } from "autobind-decorator"; import { If } from "tsx-control-statements/components"; import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; -import { GlobalModel } from "@/models"; +import { GlobalCommandRunner, GlobalModel } from "@/models"; import { isBlank } from "@/util/util"; import { WorkspaceView } from "./workspace/workspaceview"; import { PluginsView } from "./pluginsview/pluginsview"; @@ -20,6 +20,7 @@ import { MainSideBar } from "./sidebar/sidebar"; import { DisconnectedModal, ClientStopModal } from "./common/modals"; import { ModalsProvider } from "./common/modals/provider"; import { ErrorBoundary } from "./common/error/errorboundary"; +import cn from "classnames"; import "./app.less"; dayjs.extend(localizedFormat); @@ -37,7 +38,7 @@ class App extends React.Component<{}, {}> { @boundMethod handleContextMenu(e: any) { let isInNonTermInput = false; - let activeElem = document.activeElement; + const activeElem = document.activeElement; if (activeElem != null && activeElem.nodeName == "TEXTAREA") { if (!activeElem.classList.contains("xterm-helper-textarea")) { isInNonTermInput = true; @@ -46,17 +47,13 @@ class App extends React.Component<{}, {}> { if (activeElem != null && activeElem.nodeName == "INPUT" && activeElem.getAttribute("type") == "text") { isInNonTermInput = true; } - let opts: ContextMenuOpts = {}; + const opts: ContextMenuOpts = {}; if (isInNonTermInput) { opts.showCut = true; } - let sel = window.getSelection(); - if (!isBlank(sel?.toString())) { + const sel = window.getSelection(); + if (!isBlank(sel?.toString()) || isInNonTermInput) { GlobalModel.contextEditMenu(e, opts); - } else { - if (isInNonTermInput) { - GlobalModel.contextEditMenu(e, opts); - } } } @@ -67,13 +64,19 @@ class App extends React.Component<{}, {}> { })(); } + @boundMethod + openSidebar() { + const width = GlobalModel.mainSidebarModel.getWidth(true); + GlobalCommandRunner.clientSetSidebar(width, false); + } + render() { - let remotesModel = GlobalModel.remotesModel; - let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get(); - let hasClientStop = GlobalModel.getHasClientStop(); - let dcWait = this.dcWait.get(); - let platform = GlobalModel.getPlatform(); - let clientData = GlobalModel.clientData.get(); + const remotesModel = GlobalModel.remotesModel; + const disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get(); + const hasClientStop = GlobalModel.getHasClientStop(); + const dcWait = this.dcWait.get(); + const platform = GlobalModel.getPlatform(); + const clientData = GlobalModel.clientData.get(); // Previously, this is done in sidebar.tsx but it causes flicker when clientData is null cos screen-view shifts around. // Doing it here fixes the flicker cos app is not rendered until clientData is populated. @@ -106,14 +109,22 @@ class App extends React.Component<{}, {}> { setTimeout(() => this.updateDcWait(false), 0); } // used to force a full reload of the application - let renderVersion = GlobalModel.renderVersion.get(); + const renderVersion = GlobalModel.renderVersion.get(); + const sidebarCollapsed = GlobalModel.mainSidebarModel.getCollapsed(); return (
+ +
+
+ logo +
+
+
diff --git a/src/app/bookmarks/bookmarks.tsx b/src/app/bookmarks/bookmarks.tsx index 0ed16bd18..311771349 100644 --- a/src/app/bookmarks/bookmarks.tsx +++ b/src/app/bookmarks/bookmarks.tsx @@ -16,6 +16,7 @@ import { ReactComponent as TrashIcon } from "@/assets/icons/favourites/trash.svg import { ReactComponent as FavoritesIcon } from "@/assets/icons/favourites.svg"; import "./bookmarks.less"; +import { MainView } from "../common/elements/mainview"; type BookmarkProps = { bookmark: BookmarkType; @@ -23,10 +24,6 @@ type BookmarkProps = { @mobxReact.observer class Bookmark extends React.Component { - constructor(props: BookmarkProps) { - super(props); - } - @boundMethod handleDeleteClick(): void { let { bookmark } = this.props; @@ -45,14 +42,12 @@ class Bookmark extends React.Component { handleEditCancel(): void { let model = GlobalModel.bookmarksModel; model.cancelEdit(); - return; } @boundMethod handleEditUpdate(): void { let model = GlobalModel.bookmarksModel; model.confirmEdit(); - return; } @boundMethod @@ -185,31 +180,15 @@ class Bookmark extends React.Component { @mobxReact.observer class BookmarksView extends React.Component<{}, {}> { - constructor(props: {}) { - super(props); - } - - @boundMethod - closeView(): void { - GlobalModel.bookmarksModel.closeView(); - } - render() { - let isHidden = GlobalModel.activeMainView.get() != "bookmarks"; + const isHidden = GlobalModel.activeMainView.get() != "bookmarks"; if (isHidden) { return null; } let bookmarks = GlobalModel.bookmarksModel.bookmarks; - let idx: number = 0; let bookmark: BookmarkType = null; return ( -
-
-
Favorites
-
- -
-
+
@@ -238,7 +217,7 @@ class BookmarksView extends React.Component<{}, {}> {
-
+ ); } } diff --git a/src/app/clientsettings/clientsettings.tsx b/src/app/clientsettings/clientsettings.tsx index 0f23735f9..67693fc59 100644 --- a/src/app/clientsettings/clientsettings.tsx +++ b/src/app/clientsettings/clientsettings.tsx @@ -12,6 +12,7 @@ import { commandRtnHandler, isBlank } from "@/util/util"; import * as appconst from "@/app/appconst"; import "./clientsettings.less"; +import { MainView } from "../common/elements/mainview"; @mobxReact.observer class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hoveredItemId: string }> { @@ -105,11 +106,6 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove })(); } - @boundMethod - handleClose(): void { - GlobalModel.clientSettingsViewModel.closeView(); - } - @boundMethod handleChangeShortcut(newShortcut: string): void { const prtn = GlobalCommandRunner.setGlobalShortcut(newShortcut); @@ -148,13 +144,11 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove const curFontFamily = GlobalModel.getTermFontFamily(); return ( -
-
-
Client Settings
-
- -
-
+
Term Font Size
@@ -259,7 +253,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
-
+ ); } } diff --git a/src/app/common/elements/mainview.less b/src/app/common/elements/mainview.less new file mode 100644 index 000000000..09d22fb4c --- /dev/null +++ b/src/app/common/elements/mainview.less @@ -0,0 +1,55 @@ +.mainview { + flex-grow: 1; + display: flex; + flex-direction: column; + position: relative; + border-radius: 0 var(--app-border-radius) var(--app-border-radius) 0; + border-bottom: 1px solid var(--app-border-color); + border-right: 1px solid var(--app-border-color); + border-left: 1px solid var(--app-border-color); + background-color: var(--app-bg-color); + + &.is-hidden { + display: none; + } + + .header { + -webkit-app-region: drag; + display: flex; + justify-content: space-between; + flex-direction: row; + line-height: var(--screentabs-height); + vertical-align: middle; + padding: 0 10px 0 10px; + margin: 0; + + .title { + font-size: 1.5em; + padding: 0 10px; + vertical-align: middle; + line-height: var(--screentabs-height); + color: var(--term-white); + margin: 0; + } + + .close-div { + -webkit-app-region: no-drag; + font-size: 1.5em; + } + } + + .bottom-border { + border-bottom: 1px solid var(--app-border-color); + margin-bottom: 10px; + } +} + +// 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. +// More margin is given on macOS to account for the traffic light buttons +#main.platform-darwin.sidebar-collapsed .header { + margin-left: var(--floating-logo-width-darwin); +} + +#main:not(.platform-darwin).sidebar-collapsed .header { + margin-left: var(--floating-logo-width); +} diff --git a/src/app/common/elements/mainview.tsx b/src/app/common/elements/mainview.tsx new file mode 100644 index 000000000..de15d3c58 --- /dev/null +++ b/src/app/common/elements/mainview.tsx @@ -0,0 +1,38 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import cn from "classnames"; +import { GlobalModel } from "@/models"; + +import "./mainview.less"; + +@mobxReact.observer +class MainView extends React.Component<{ + viewName: string; + title: string; + onClose: () => void; + children: React.ReactNode; +}> { + render() { + // TODO: This is a workaround for History view not honoring the sidebar width. This is rooted in the table width for the history view, which uses `calc(100%-20px)`. To properly fix this, History view needs a full overhaul. + const width = window.innerWidth - 6 - GlobalModel.mainSidebarModel.getWidth(); + + return ( +
+
+
+
{this.props.title}
+
+ +
+
+
+ {this.props.children} +
+ ); + } +} + +export { MainView }; diff --git a/src/app/common/elements/viewheader.less b/src/app/common/elements/viewheader.less new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/common/elements/viewheader.tsx b/src/app/common/elements/viewheader.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/connections/connections.tsx b/src/app/connections/connections.tsx index 444b3a0b6..fa1dd5325 100644 --- a/src/app/connections/connections.tsx +++ b/src/app/connections/connections.tsx @@ -12,6 +12,7 @@ import { Button, Status, ShowWaveShellInstallPrompt } from "@/common/elements"; import * as util from "@/util/util"; import "./connections.less"; +import { MainView } from "../common/elements/mainview"; @mobxReact.observer class ConnectionsView extends React.Component<{ model: RemotesModel }, { hoveredItemId: string }> { @@ -97,11 +98,6 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered } } - @boundMethod - handleClose(): void { - GlobalModel.connectionViewModel.closeView(); - } - componentDidMount() { if (this.tableRef.current != null) { this.tableRszObs = new ResizeObserver(this.handleTableResize.bind(this)); @@ -130,13 +126,7 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered let item: RemoteType = null; return ( -
-
-
Connections
-
- -
-
+ No Connections Items Found - + ); } } diff --git a/src/app/history/history.less b/src/app/history/history.less index 0a141834c..d39871b85 100644 --- a/src/app/history/history.less +++ b/src/app/history/history.less @@ -1,10 +1,6 @@ .history-view { padding-bottom: 10px; - .header { - padding: 18px 18px; - } - .icon { width: 2em !important; height: 1.3em !important; @@ -43,23 +39,7 @@ top: 0.5em; } - .close-div { - position: absolute; - right: 1em; - top: 0.8em; - cursor: pointer; - width: 1.5em; - height: 1.5em; - border-radius: 50%; - svg { - width: 1.5em; - height: 1.5em; - fill: var(--app-text-color); - } - } - .history-search { - flex-grow: 1; padding: 0 10px 10px 10px; input { diff --git a/src/app/history/history.tsx b/src/app/history/history.tsx index 14e4fa8d6..232c4a5aa 100644 --- a/src/app/history/history.tsx +++ b/src/app/history/history.tsx @@ -28,6 +28,7 @@ import { ReactComponent as CheckIcon } from "@/assets/icons/line/check.svg"; import { ReactComponent as CopyIcon } from "@/assets/icons/history/copy.svg"; import "./history.less"; +import { MainView } from "../common/elements/mainview"; dayjs.extend(customParseFormat); dayjs.extend(localizedFormat); @@ -178,11 +179,6 @@ class HistoryView extends React.Component<{}, {}> { remoteDropdownActive: OV = mobx.observable.box(false, { name: "remoteDropdownActive" }); copiedItemId: OV = mobx.observable.box(null, { name: "copiedItemId" }); - @boundMethod - clickCloseHandler(): void { - GlobalModel.historyViewModel.closeView(); - } - @boundMethod handleNext() { GlobalModel.historyViewModel.goNext(); @@ -206,7 +202,6 @@ class HistoryView extends React.Component<{}, {}> { if (checkKeyPressed(waveEvent, "Enter")) { e.preventDefault(); GlobalModel.historyViewModel.submitSearch(); - return; } } @@ -229,10 +224,9 @@ class HistoryView extends React.Component<{}, {}> { let numSelected = hvm.selectedItems.size; if (numSelected > 0) { hvm.selectedItems.clear(); - return; } else { - for (let i = 0; i < hvm.items.length; i++) { - hvm.selectedItems.set(hvm.items[i].historyid, true); + for (const element of hvm.items) { + hvm.selectedItems.set(element.historyid, true); } } })(); @@ -302,7 +296,6 @@ class HistoryView extends React.Component<{}, {}> { return; } hvm.setFromDate(e.target.value); - return; } @boundMethod @@ -399,7 +392,6 @@ class HistoryView extends React.Component<{}, {}> { return null; } let hvm = GlobalModel.historyViewModel; - let idx: number = 0; let item: HistoryItem = null; let items = hvm.items.slice(); let nowDate = new Date(); @@ -410,32 +402,13 @@ class HistoryView extends React.Component<{}, {}> { let offset = hvm.offset.get(); let numSelected = hvm.selectedItems.size; let activeItemId = hvm.activeItem.get(); - let activeItem = hvm.getHistoryItemById(activeItemId); - let activeLine: LineType = null; - if (activeItem != null) { - activeLine = hvm.getLineById(activeItem.lineid); - } let sessionIds = Object.keys(snames); let sessionId: string = null; let remoteIds = Object.keys(rnames); let remoteId: string = null; - // TODO: something is weird with how we calculate width for views. Before, history view was not honoring tab width. This fix is copied from workspaceview.tsx, which had a similar issue. - const width = window.innerWidth - 6 - GlobalModel.mainSidebarModel.getWidth(); return ( -
-
-
History
-
- -
-
- +
{
-
+ ); } } class LineContainer extends React.Component<{ historyId: string; width: number }, {}> { line: LineType; - cmd: Cmd; historyItem: HistoryItem; visible: OV = mobx.observable.box(true); overrideCollapsed: OV = mobx.observable.box(false); @@ -698,7 +670,6 @@ class LineContainer extends React.Component<{ historyId: string; width: number } return; } this.line = hvm.getLineById(this.historyItem.lineid); - this.cmd = hvm.getCmdById(this.historyItem.lineid); } @boundMethod diff --git a/src/app/root.less b/src/app/root.less index 597654275..0b5772f0e 100644 --- a/src/app/root.less +++ b/src/app/root.less @@ -26,6 +26,11 @@ --screentabs-font-weight: 300; --screentabs-selected-font-weight: 300; + // floating logo settings + --floating-logo-width-darwin: 110px; + --floating-logo-width: 40px; + --floating-logo-height: var(--screentabs-height); + // global colors --app-accent-color: rgb(88, 193, 66); --app-error-color: rgb(204, 0, 0); diff --git a/src/app/workspace/screen/tab.tsx b/src/app/workspace/screen/tab.tsx index 71f645013..432ccba4c 100644 --- a/src/app/workspace/screen/tab.tsx +++ b/src/app/workspace/screen/tab.tsx @@ -8,7 +8,6 @@ import { boundMethod } from "autobind-decorator"; import cn from "classnames"; import { GlobalModel, GlobalCommandRunner, Screen } from "@/models"; import { ActionsIcon, StatusIndicator, CenteredIcon } from "@/common/icons/icons"; -import { renderCmdText } from "@/elements"; import { ReactComponent as SquareIcon } from "@/assets/icons/tab/square.svg"; import * as constants from "@/app/appconst"; import { Reorder } from "framer-motion"; diff --git a/src/app/workspace/screen/tabs.less b/src/app/workspace/screen/tabs.less index 87c7e4cee..236ccf7f9 100644 --- a/src/app/workspace/screen/tabs.less +++ b/src/app/workspace/screen/tabs.less @@ -91,34 +91,6 @@ overflow: hidden; height: var(--screentabs-height); - &.sidebar-collapsed .logo-button-container { - width: 105px; - flex-shrink: 0; - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; - -webkit-app-region: drag; - - .logo-button { - width: 25px; - height: 25px; - margin-right: 6px; - cursor: pointer; - user-select: none; - -webkit-app-region: no-drag; - - &:hover { - background-color: #333; - border-radius: 4px; - } - } - } - - .logo-button { - width: 20px; - } - &:hover { z-index: 200; } @@ -206,3 +178,13 @@ height: 100%; } } + +// 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. +// More margin is given on macOS to account for the traffic light buttons +#main.platform-darwin.sidebar-collapsed .screen-tabs-container { + margin-left: var(--floating-logo-width-darwin); +} + +#main:not(.platform-darwin).sidebar-collapsed .screen-tabs-container { + margin-left: var(--floating-logo-width); +} diff --git a/src/app/workspace/screen/tabs.tsx b/src/app/workspace/screen/tabs.tsx index 6287a4403..c2c85b478 100644 --- a/src/app/workspace/screen/tabs.tsx +++ b/src/app/workspace/screen/tabs.tsx @@ -168,12 +168,6 @@ class ScreenTabs extends React.Component< // For touchpad events, do nothing and let the browser handle it } - @boundMethod - openSidebar() { - const width = GlobalModel.mainSidebarModel.getWidth(true); - GlobalCommandRunner.clientSetSidebar(width, false); - } - render() { let { showingScreens } = this.state; let { session } = this.props; @@ -183,20 +177,8 @@ class ScreenTabs extends React.Component< let screen: Screen | null = null; let index = 0; let activeScreenId = this.getActiveScreenId(); - const sidebarCollapsed = GlobalModel.mainSidebarModel.getCollapsed(); return ( -
- -
-
- logo -
-
-
+
{/* Inner container ensures that hovering over the scrollbar doesn't trigger the hover effect on the tabs. This prevents weird flickering of the icons when the mouse is moved over the scrollbar. */}