mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Move icons out of common, clean up visibility story (#251)
This commit is contained in:
parent
34ec4ff39f
commit
b97423268c
@ -1151,42 +1151,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.front-icon {
|
||||
margin-right: 5px;
|
||||
.svg-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.positional-icon-inner {
|
||||
& > div,i {
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
margin: auto auto;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
.icon {
|
||||
font-size: 15px;
|
||||
padding-top: 2.5px;
|
||||
margin-bottom: -2.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
&.error {
|
||||
color: @term-red;
|
||||
}
|
||||
&.success {
|
||||
color: @term-green;
|
||||
}
|
||||
&.output {
|
||||
color: @term-white;
|
||||
}
|
||||
}
|
||||
|
@ -1241,81 +1241,6 @@ class Modal extends React.Component<ModalProps> {
|
||||
}
|
||||
}
|
||||
|
||||
interface PositionalIconProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
class FrontIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div className="front-icon positional-icon">
|
||||
<div className="positional-icon-inner">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EndIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div className="end-icon positional-icon">
|
||||
<div className="positional-icon-inner">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ActionsIconProps {
|
||||
onClick: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
class ActionsIcon extends React.Component<ActionsIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div onClick={this.props.onClick} title="Actions" className="actions">
|
||||
<div className="icon hoverEffect fa-sharp fa-solid fa-1x fa-ellipsis-vertical"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface StatusIndicatorProps {
|
||||
level: StatusIndicatorLevel;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
||||
render() {
|
||||
const statusIndicatorLevel = this.props.level;
|
||||
let statusIndicator = null;
|
||||
if (statusIndicatorLevel != StatusIndicatorLevel.None) {
|
||||
let statusIndicatorClass = null;
|
||||
switch (statusIndicatorLevel) {
|
||||
case StatusIndicatorLevel.Output:
|
||||
statusIndicatorClass = "output";
|
||||
break;
|
||||
case StatusIndicatorLevel.Success:
|
||||
statusIndicatorClass = "success";
|
||||
break;
|
||||
case StatusIndicatorLevel.Error:
|
||||
statusIndicatorClass = "error";
|
||||
break;
|
||||
}
|
||||
statusIndicator = (
|
||||
<div
|
||||
className={`${this.props.className} fa-sharp fa-solid fa-circle-small status-indicator ${statusIndicatorClass}`}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
return statusIndicator;
|
||||
}
|
||||
}
|
||||
|
||||
function ShowWaveShellInstallPrompt(callbackFn: () => void) {
|
||||
let message: string = `
|
||||
In order to use Wave's advanced features like unified history and persistent sessions, Wave installs a small, open-source helper program called WaveShell on your remote machine. WaveShell does not open any external ports and only communicates with your *local* Wave terminal instance over ssh. For more information please see [the docs](https://docs.waveterm.dev/reference/waveshell).
|
||||
@ -1358,9 +1283,5 @@ export {
|
||||
LinkButton,
|
||||
Status,
|
||||
Modal,
|
||||
FrontIcon,
|
||||
EndIcon,
|
||||
ActionsIcon,
|
||||
StatusIndicator,
|
||||
ShowWaveShellInstallPrompt,
|
||||
};
|
||||
|
58
src/app/common/icons/icons.less
Normal file
58
src/app/common/icons/icons.less
Normal file
@ -0,0 +1,58 @@
|
||||
@import "../../../app/common/themes/themes.less";
|
||||
|
||||
.front-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.positional-icon-visible {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.positional-icon-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.positional-icon {
|
||||
display: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
.svg-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.positional-icon-inner {
|
||||
display: flex;
|
||||
width: inherit;
|
||||
height: 100%;
|
||||
line-height: inherit;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
& > div,i,.svg-icon,span {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
.icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
.error {
|
||||
color: @term-red;
|
||||
}
|
||||
.success {
|
||||
color: @term-green;
|
||||
}
|
||||
.output {
|
||||
color: @term-white;
|
||||
}
|
||||
}
|
75
src/app/common/icons/icons.tsx
Normal file
75
src/app/common/icons/icons.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React from "react";
|
||||
import { StatusIndicatorLevel } from "../../../types/types";
|
||||
import cn from "classnames";
|
||||
|
||||
interface PositionalIconProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class FrontIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div className={cn("front-icon", "positional-icon", this.props.className)}>
|
||||
<div className="positional-icon-inner">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CenteredIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div className={cn("centered-icon", "positional-icon", this.props.className)} onClick={this.props.onClick}>
|
||||
<div className="positional-icon-inner">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface ActionsIconProps {
|
||||
onClick: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class ActionsIcon extends React.Component<ActionsIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<CenteredIcon className="actions" onClick={this.props.onClick}>
|
||||
<div className="icon hoverEffect fa-sharp fa-solid fa-1x fa-ellipsis-vertical"></div>
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface StatusIndicatorProps {
|
||||
level: StatusIndicatorLevel;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
||||
render() {
|
||||
const statusIndicatorLevel = this.props.level;
|
||||
let statusIndicator = null;
|
||||
if (statusIndicatorLevel != StatusIndicatorLevel.None) {
|
||||
let statusIndicatorClass = null;
|
||||
switch (statusIndicatorLevel) {
|
||||
case StatusIndicatorLevel.Output:
|
||||
statusIndicatorClass = "output";
|
||||
break;
|
||||
case StatusIndicatorLevel.Success:
|
||||
statusIndicatorClass = "success";
|
||||
break;
|
||||
case StatusIndicatorLevel.Error:
|
||||
statusIndicatorClass = "error";
|
||||
break;
|
||||
}
|
||||
statusIndicator = (
|
||||
<CenteredIcon className={cn(this.props.className, "status-indicator")}>
|
||||
<div className={cn(statusIndicatorClass, "fa-sharp", "fa-solid", "fa-circle-small")}></div>
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
return statusIndicator;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
@import "../../app/common/themes/themes.less";
|
||||
@import "../../app/common/icons/icons.less";
|
||||
|
||||
.main-sidebar {
|
||||
padding: 0;
|
||||
@ -23,6 +24,7 @@
|
||||
&.collapsed {
|
||||
width: 6em;
|
||||
min-width: 6em;
|
||||
|
||||
.arrow-container,
|
||||
.collapse-button {
|
||||
transform: rotate(180deg);
|
||||
@ -125,12 +127,9 @@
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||
}
|
||||
.index {
|
||||
margin: 0 1.5em 0 -0.5rem;
|
||||
font-size: 0.8em;
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
display: inline-block;
|
||||
}
|
||||
.hotkey {
|
||||
float: left !important;
|
||||
@ -150,7 +149,7 @@
|
||||
|
||||
.item {
|
||||
padding: 5px;
|
||||
margin: 0 6px;
|
||||
margin-left: 6px;
|
||||
border-radius: 4px;
|
||||
opacity: 1;
|
||||
transition: opacity 0.1s ease-in-out, visibility 0.1s step-end;
|
||||
@ -161,50 +160,43 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.front-icon {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
|
||||
.end-icons {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.item-contents {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.icon {
|
||||
margin: -2px 8px 0px 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.actions.icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.hotkey {
|
||||
float: right;
|
||||
margin-right: 6px;
|
||||
display: none;
|
||||
letter-spacing: 6px;
|
||||
}
|
||||
.disabled .hotkey {
|
||||
display: none;
|
||||
}
|
||||
.actions {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
.hotkey {
|
||||
display: block;
|
||||
:not(.disabled) .hotkey {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
.actions {
|
||||
display: block;
|
||||
}
|
||||
.status-indicator {
|
||||
display: none;
|
||||
.positional-icon-visible;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:hover) .status-indicator {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
.add_workspace {
|
||||
float: right;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 2px;
|
||||
margin-right: 6px;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
vertical-align: middle;
|
||||
svg {
|
||||
fill: @base-color;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { ReactComponent as LeftChevronIcon } from "../assets/icons/chevron_left.
|
||||
import { ReactComponent as AppsIcon } from "../assets/icons/apps.svg";
|
||||
import { ReactComponent as WorkspacesIcon } from "../assets/icons/workspaces.svg";
|
||||
import { ReactComponent as AddIcon } from "../assets/icons/add.svg";
|
||||
import { ReactComponent as SettingsIcon } from "../assets/icons/settings.svg";
|
||||
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, Session, VERSION } from "../../model/model";
|
||||
@ -22,7 +23,7 @@ import { isBlank, openLink } from "../../util/util";
|
||||
import * as constants from "../appconst";
|
||||
|
||||
import "./sidebar.less";
|
||||
import { ActionsIcon, EndIcon, FrontIcon, StatusIndicator } from "../common/common";
|
||||
import { ActionsIcon, CenteredIcon, FrontIcon, StatusIndicator } from "../common/icons/icons";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
@ -31,26 +32,34 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
class SideBarItem extends React.Component<{
|
||||
frontIcon: React.ReactNode;
|
||||
contents: React.ReactNode | string;
|
||||
endIcon?: React.ReactNode[];
|
||||
endIcons?: React.ReactNode[];
|
||||
className?: string;
|
||||
key?: React.Key;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
key={this.props.key}
|
||||
className={cn("item", "unselectable", "hoverEffect", this.props.className)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<FrontIcon>{this.props.frontIcon}</FrontIcon>
|
||||
<div className="item-contents truncate">{this.props.contents}</div>
|
||||
<EndIcon>{this.props.endIcon}</EndIcon>
|
||||
<div className="end-icons">{this.props.endIcons}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HotKeyIcon extends React.Component<{ hotkey: string }> {
|
||||
render() {
|
||||
return (
|
||||
<CenteredIcon className="hotkey">
|
||||
<span>⌘{this.props.hotkey}</span>
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class MainSideBar extends React.Component<{}, {}> {
|
||||
collapsed: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
@ -180,15 +189,12 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
const sessionIndicator = Math.max(...sessionScreens.map((screen) => screen.statusIndicator.get()));
|
||||
return (
|
||||
<SideBarItem
|
||||
key={index}
|
||||
className={`${isActive ? "active" : ""}`}
|
||||
frontIcon={<span className="index">{index + 1}</span>}
|
||||
contents={session.name.get()}
|
||||
endIcon={[
|
||||
<StatusIndicator level={sessionIndicator} />,
|
||||
<ActionsIcon
|
||||
onClick={(e) => this.openSessionSettings(e, session)}
|
||||
/>,
|
||||
endIcons={[
|
||||
<StatusIndicator key="statusindicator" level={sessionIndicator} />,
|
||||
<ActionsIcon key="actions" onClick={(e) => this.openSessionSettings(e, session)} />,
|
||||
]}
|
||||
onClick={() => this.handleSessionClick(session.sessionId)}
|
||||
/>
|
||||
@ -228,12 +234,12 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
<SideBarItem
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-clock-rotate-left icon" />}
|
||||
contents="History"
|
||||
endIcon={[<span className="hotkey">⌘H</span>]}
|
||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="H" />]}
|
||||
onClick={this.handleHistoryClick}
|
||||
/>
|
||||
{/* <SideBarItem className="hoverEffect unselectable" frontIcon={<FavoritesIcon className="icon" />} contents="Favorites" endIcon={<span className="hotkey">⌘B</span>} onClick={this.handleBookmarksClick}/> */}
|
||||
<SideBarItem
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-globe icon "/>}
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-globe icon " />}
|
||||
contents="Connections"
|
||||
onClick={this.handleConnectionsClick}
|
||||
/>
|
||||
@ -242,8 +248,12 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
<SideBarItem
|
||||
frontIcon={<WorkspacesIcon className="icon" />}
|
||||
contents="Workspaces"
|
||||
endIcon={[
|
||||
<div className="add_workspace hoverEffect" onClick={this.handleNewSession}>
|
||||
endIcons={[
|
||||
<div
|
||||
key="add_workspace"
|
||||
className="add_workspace hoverEffect"
|
||||
onClick={this.handleNewSession}
|
||||
>
|
||||
<AddIcon />
|
||||
</div>,
|
||||
]}
|
||||
@ -263,11 +273,11 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
frontIcon={<AppsIcon className="icon" />}
|
||||
contents="Apps"
|
||||
onClick={this.handlePluginsClick}
|
||||
endIcon={[<span className="hotkey">⌘A</span>]}
|
||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="A" />]}
|
||||
/>
|
||||
</If>
|
||||
<SideBarItem
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-gear icon"/>}
|
||||
frontIcon={<SettingsIcon className="icon" />}
|
||||
contents="Settings"
|
||||
onClick={this.handleSettingsClick}
|
||||
/>
|
||||
|
@ -7,7 +7,8 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../model/model";
|
||||
import { ActionsIcon, EndIcon, StatusIndicator, renderCmdText } from "../../common/common";
|
||||
import { ActionsIcon, StatusIndicator, CenteredIcon } from "../../common/icons/icons";
|
||||
import { renderCmdText } from "../../common/common";
|
||||
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
||||
import * as constants from "../../appconst";
|
||||
import { Reorder } from "framer-motion";
|
||||
@ -79,7 +80,11 @@ class ScreenTab extends React.Component<
|
||||
|
||||
let tabIndex = null;
|
||||
if (index + 1 <= 9) {
|
||||
tabIndex = <div className="tab-index">{renderCmdText(String(index + 1))}</div>;
|
||||
tabIndex = (
|
||||
<CenteredIcon className="tab-index">
|
||||
<div>{renderCmdText(String(index + 1))}</div>
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
let archived = screen.archived.get() ? (
|
||||
<i title="archived" className="fa-sharp fa-solid fa-box-archive" />
|
||||
@ -109,19 +114,17 @@ class ScreenTab extends React.Component<
|
||||
onContextMenu={(event) => this.openScreenSettings(event, screen)}
|
||||
onDragEnd={this.handleDragEnd}
|
||||
>
|
||||
<div className="front-icon">
|
||||
{this.renderTabIcon(screen)}
|
||||
</div>
|
||||
<CenteredIcon className="front-icon">{this.renderTabIcon(screen)}</CenteredIcon>
|
||||
<div className="tab-name truncate">
|
||||
{archived}
|
||||
{webShared}
|
||||
{screen.name.get()}
|
||||
</div>
|
||||
<EndIcon>
|
||||
<StatusIndicator level={statusIndicatorLevel}/>
|
||||
<div className="end-icons">
|
||||
<StatusIndicator level={statusIndicatorLevel} />
|
||||
{tabIndex}
|
||||
<ActionsIcon onClick={(e) => this.openScreenSettings(e, screen)} />
|
||||
</EndIcon>
|
||||
</div>
|
||||
</Reorder.Item>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
@import "../../../app/common/themes/themes.less";
|
||||
@import "../../../app/common/icons/icons.less";
|
||||
|
||||
#main .screen-tabs .screen-tab {
|
||||
border-top: 1px solid transparent;
|
||||
@ -238,7 +239,6 @@
|
||||
|
||||
.screen-tabs-container-inner {
|
||||
overflow-x: scroll;
|
||||
|
||||
}
|
||||
|
||||
.screen-tabs {
|
||||
@ -257,14 +257,7 @@
|
||||
padding: 0 8px 0 8px;
|
||||
|
||||
.front-icon {
|
||||
margin-right: 5px;
|
||||
.svg-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.fa-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
.positional-icon-visible;
|
||||
}
|
||||
|
||||
.tab-name {
|
||||
@ -283,40 +276,27 @@
|
||||
}
|
||||
|
||||
// Only one of these will be visible at a time
|
||||
.end-icon {
|
||||
.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 -8px 0 0;
|
||||
.status-indicator {
|
||||
display: block;
|
||||
}
|
||||
.actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-index {
|
||||
display: none;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .screen-tab {
|
||||
.tab-index {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: none;
|
||||
}
|
||||
&:not(:hover) .status-indicator{
|
||||
.positional-icon-visible;
|
||||
}
|
||||
|
||||
&:hover .screen-tab:hover .tab-index {
|
||||
display: none;
|
||||
&:hover {
|
||||
.screen-tab:not(:hover) .tab-index {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
.screen-tab:hover .actions {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user