mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15d02f3357 | |||
| 39d371681c | |||
| 412c3ac5a2 | |||
| 36484621ec | |||
| 72231ead1d | |||
| 81c1fd43f7 | |||
| c9a73c2204 | |||
| 434f980b49 | |||
| 75d462764d | |||
| c58c88af4e | |||
| 05be301ea2 | |||
| 27e5a9154d | |||
| a7d2148bc0 | |||
| ead75880e1 | |||
| 0bdb50cf91 | |||
| abf3448f07 | |||
| 8988d26867 | |||
| a084ca4801 | |||
| 36e2578a03 | |||
| a49487c035 | |||
| 03296043fb | |||
| ec797f55ff | |||
| 550461167b | |||
| cf5d2ecddf | |||
| c4128cd266 | |||
| 17bb07a47d |
@@ -9,7 +9,7 @@ import { If, For } from "tsx-control-statements/components";
|
|||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import type { BookmarkType } from "../../types/types";
|
import type { BookmarkType } from "../../types/types";
|
||||||
import { GlobalModel } from "../../model/model";
|
import { GlobalModel } from "../../model/model";
|
||||||
import { CmdStrCode, Markdown } from "../common/common";
|
import { CmdStrCode, Markdown } from "../common/elements";
|
||||||
|
|
||||||
import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg";
|
import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg";
|
||||||
import { ReactComponent as CopyIcon } from "../assets/icons/favourites/copy.svg";
|
import { ReactComponent as CopyIcon } from "../assets/icons/favourites/copy.svg";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
|||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { GlobalModel, GlobalCommandRunner, MinFontSize, MaxFontSize, RemotesModel } from "../../model/model";
|
import { GlobalModel, GlobalCommandRunner, MinFontSize, MaxFontSize, RemotesModel } from "../../model/model";
|
||||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "../common/common";
|
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "../common/elements";
|
||||||
import { CommandRtnType, ClientDataType } from "../../types/types";
|
import { CommandRtnType, ClientDataType } from "../../types/types";
|
||||||
import { commandRtnHandler, isBlank } from "../../util/util";
|
import { commandRtnHandler, isBlank } from "../../util/util";
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,123 @@
|
|||||||
|
@import "../../../app/common/themes/themes.less";
|
||||||
|
|
||||||
|
.wave-button {
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
border: none;
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: inherit;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
padding: 6px 16px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @term-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
fill: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
color: @term-green;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
i {
|
||||||
|
fill: @term-green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.solid {
|
||||||
|
color: @term-bright-white;
|
||||||
|
background: @term-green;
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||||
|
0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset;
|
||||||
|
|
||||||
|
i {
|
||||||
|
fill: @term-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.outlined {
|
||||||
|
border: 1px solid @term-green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ghost {
|
||||||
|
// Styles for .ghost are already defined above
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @term-bright-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
color: @term-white;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
&.solid {
|
||||||
|
background: rgba(255, 255, 255, 0.09);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.outlined {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ghost {
|
||||||
|
padding: 6px 10px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
fill: @term-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.color-yellow {
|
||||||
|
&.solid {
|
||||||
|
border-color: @warning-yellow;
|
||||||
|
background-color: mix(@warning-yellow, @term-white, 50%);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.outlined {
|
||||||
|
color: @warning-yellow;
|
||||||
|
border-color: @warning-yellow;
|
||||||
|
&:hover {
|
||||||
|
color: @term-white;
|
||||||
|
border-color: @term-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ghost {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.color-red {
|
||||||
|
&.solid {
|
||||||
|
border-color: @term-red;
|
||||||
|
background-color: mix(@term-red, @term-white, 50%);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.outlined {
|
||||||
|
color: @term-red;
|
||||||
|
border-color: @term-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ghost {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.link-button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { boundMethod } from "autobind-decorator";
|
||||||
|
import cn from "classnames";
|
||||||
|
|
||||||
|
import "./button.less";
|
||||||
|
|
||||||
|
type ButtonVariantType = "outlined" | "solid" | "ghost";
|
||||||
|
type ButtonThemeType = "primary" | "secondary";
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
theme?: ButtonThemeType;
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
variant?: ButtonVariantType;
|
||||||
|
leftIcon?: React.ReactNode;
|
||||||
|
rightIcon?: React.ReactNode;
|
||||||
|
color?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Button extends React.Component<ButtonProps> {
|
||||||
|
static defaultProps = {
|
||||||
|
theme: "primary",
|
||||||
|
variant: "solid",
|
||||||
|
color: "",
|
||||||
|
style: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleClick() {
|
||||||
|
if (this.props.onClick && !this.props.disabled) {
|
||||||
|
this.props.onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { leftIcon, rightIcon, theme, children, disabled, variant, color, style, autoFocus, className } =
|
||||||
|
this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn("wave-button", theme, variant, color, { disabled: disabled }, className)}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
disabled={disabled}
|
||||||
|
style={style}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
>
|
||||||
|
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||||
|
{children}
|
||||||
|
{rightIcon && <span className="icon-right">{rightIcon}</span>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Button };
|
||||||
|
export type { ButtonProps };
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
@import "../../../app/common/themes/themes.less";
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] + label {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: @term-bright-white;
|
||||||
|
transition: color 250ms cubic-bezier(0.4, 0, 0.23, 1);
|
||||||
|
}
|
||||||
|
input[type="checkbox"] + label > span {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid #9e9e9e;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 250ms cubic-bezier(0.4, 0, 0.23, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] + label:hover > span,
|
||||||
|
input[type="checkbox"]:focus + label > span {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked + label > ins {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked + label > span {
|
||||||
|
border: 10px solid @term-green;
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked + label > span:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 7px;
|
||||||
|
height: 12px;
|
||||||
|
border-right: 2px solid #fff;
|
||||||
|
border-bottom: 2px solid #fff;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
transform-origin: 0% 100%;
|
||||||
|
animation: checkbox-check 500ms cubic-bezier(0.4, 0, 0.23, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes checkbox-check {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as mobx from "mobx";
|
||||||
|
import cn from "classnames";
|
||||||
|
|
||||||
|
import "./checkbox.less";
|
||||||
|
|
||||||
|
class Checkbox extends React.Component<
|
||||||
|
{
|
||||||
|
checked?: boolean;
|
||||||
|
defaultChecked?: boolean;
|
||||||
|
onChange: (value: boolean) => void;
|
||||||
|
label: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
id?: string;
|
||||||
|
},
|
||||||
|
{ checkedInternal: boolean }
|
||||||
|
> {
|
||||||
|
generatedId;
|
||||||
|
static idCounter = 0;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
checkedInternal: this.props.checked ?? Boolean(this.props.defaultChecked),
|
||||||
|
};
|
||||||
|
this.generatedId = `checkbox-${Checkbox.idCounter++}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.checked !== undefined && this.props.checked !== prevProps.checked) {
|
||||||
|
this.setState({ checkedInternal: this.props.checked });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
const newChecked = e.target.checked;
|
||||||
|
if (this.props.checked === undefined) {
|
||||||
|
this.setState({ checkedInternal: newChecked });
|
||||||
|
}
|
||||||
|
this.props.onChange(newChecked);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { label, className, id } = this.props;
|
||||||
|
const { checkedInternal } = this.state;
|
||||||
|
const checkboxId = id || this.generatedId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("checkbox", className)}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={checkboxId}
|
||||||
|
checked={checkedInternal}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
aria-checked={checkedInternal}
|
||||||
|
role="checkbox"
|
||||||
|
/>
|
||||||
|
<label htmlFor={checkboxId}>
|
||||||
|
<span></span>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Checkbox };
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
@import "../../../app/common/themes/themes.less";
|
||||||
|
|
||||||
|
.cmdstr-code {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0px 10px 0px 0;
|
||||||
|
|
||||||
|
&.is-large {
|
||||||
|
.use-button {
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-div code {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.limit-height .code-div {
|
||||||
|
max-height: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.limit-height.is-large .code-div {
|
||||||
|
max-height: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.use-button {
|
||||||
|
flex-grow: 0;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 3px 0 0 3px;
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-self: flex-start;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-div {
|
||||||
|
background-color: @term-black;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
min-width: 100px;
|
||||||
|
overflow: auto;
|
||||||
|
border-left: 1px solid #777;
|
||||||
|
|
||||||
|
code {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 100px;
|
||||||
|
color: @term-white;
|
||||||
|
white-space: pre;
|
||||||
|
padding: 2px 8px 2px 8px;
|
||||||
|
background-color: @term-black;
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: @fixed-font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-control {
|
||||||
|
width: 0;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
.inner-copy {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -1px;
|
||||||
|
right: -20px;
|
||||||
|
|
||||||
|
padding: 2px;
|
||||||
|
padding-left: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 20px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @term-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .copy-control {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copied-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: @term-white;
|
||||||
|
opacity: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
animation-name: fade-in-out;
|
||||||
|
animation-duration: 0.3s;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { boundMethod } from "autobind-decorator";
|
||||||
|
import cn from "classnames";
|
||||||
|
import { If } from "tsx-control-statements/components";
|
||||||
|
|
||||||
|
import { ReactComponent as CheckIcon } from "../../assets/icons/line/check.svg";
|
||||||
|
import { ReactComponent as CopyIcon } from "../../assets/icons/history/copy.svg";
|
||||||
|
|
||||||
|
import "./cmdstrcode.less";
|
||||||
|
|
||||||
|
class CmdStrCode extends React.Component<
|
||||||
|
{
|
||||||
|
cmdstr: string;
|
||||||
|
onUse: () => void;
|
||||||
|
onCopy: () => void;
|
||||||
|
isCopied: boolean;
|
||||||
|
fontSize: "normal" | "large";
|
||||||
|
limitHeight: boolean;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
> {
|
||||||
|
@boundMethod
|
||||||
|
handleUse(e: any) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (this.props.onUse != null) {
|
||||||
|
this.props.onUse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleCopy(e: any) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (this.props.onCopy != null) {
|
||||||
|
this.props.onCopy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { isCopied, cmdstr, fontSize, limitHeight } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={cn("cmdstr-code", { "is-large": fontSize == "large" }, { "limit-height": limitHeight })}>
|
||||||
|
<If condition={isCopied}>
|
||||||
|
<div key="copied" className="copied-indicator">
|
||||||
|
<div>copied</div>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<div key="use" className="use-button hoverEffect" title="Use Command" onClick={this.handleUse}>
|
||||||
|
<CheckIcon className="icon" />
|
||||||
|
</div>
|
||||||
|
<div key="code" className="code-div">
|
||||||
|
<code>{cmdstr}</code>
|
||||||
|
</div>
|
||||||
|
<div key="copy" className="copy-control hoverEffect">
|
||||||
|
<div className="inner-copy" onClick={this.handleCopy} title="copy">
|
||||||
|
<CopyIcon className="icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CmdStrCode };
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
function renderCmdText(text: string): any {
|
||||||
|
return <span>⌘{text}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { renderCmdText };
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
@import "../../../app/common/themes/themes.less";
|
||||||
|
|
||||||
|
.wave-dropdown {
|
||||||
|
position: relative;
|
||||||
|
height: 44px;
|
||||||
|
min-width: 150px;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(241, 246, 243, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||||
|
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||||
|
|
||||||
|
&.no-label {
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 16px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
color: @term-white;
|
||||||
|
line-height: 10px;
|
||||||
|
|
||||||
|
&.float {
|
||||||
|
font-size: 10px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offset-left {
|
||||||
|
left: 42px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-display {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
bottom: 5px;
|
||||||
|
|
||||||
|
&.offset-left {
|
||||||
|
left: 42px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow-rotate {
|
||||||
|
transform: translateY(-50%) rotate(180deg); // Rotate the arrow when dropdown is open
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&-highlighted,
|
||||||
|
&:hover {
|
||||||
|
background: rgba(241, 246, 243, 0.08);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||||
|
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-input-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-input-decoration.end-position {
|
||||||
|
margin-right: 44px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-input-decoration.start-position {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
border-color: @term-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: @term-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 6px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #151715;
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5), 0px 3px 8px 0px rgba(0, 0, 0, 0.35), 0px 0px 0.5px 0px #fff inset,
|
||||||
|
0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-dropdown-menu-close {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as mobxReact from "mobx-react";
|
||||||
|
import { boundMethod } from "autobind-decorator";
|
||||||
|
import cn from "classnames";
|
||||||
|
import { If } from "tsx-control-statements/components";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
|
import "./dropdown.less";
|
||||||
|
|
||||||
|
interface DropdownDecorationProps {
|
||||||
|
startDecoration?: React.ReactNode;
|
||||||
|
endDecoration?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownProps {
|
||||||
|
label?: string;
|
||||||
|
options: { value: string; label: string }[];
|
||||||
|
value?: string;
|
||||||
|
className?: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
decoration?: DropdownDecorationProps;
|
||||||
|
defaultValue?: string;
|
||||||
|
required?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownState {
|
||||||
|
isOpen: boolean;
|
||||||
|
internalValue: string;
|
||||||
|
highlightedIndex: number;
|
||||||
|
isTouched: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mobxReact.observer
|
||||||
|
class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||||
|
wrapperRef: React.RefObject<HTMLDivElement>;
|
||||||
|
menuRef: React.RefObject<HTMLDivElement>;
|
||||||
|
timeoutId: any;
|
||||||
|
|
||||||
|
constructor(props: DropdownProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOpen: false,
|
||||||
|
internalValue: props.defaultValue || "",
|
||||||
|
highlightedIndex: -1,
|
||||||
|
isTouched: false,
|
||||||
|
};
|
||||||
|
this.wrapperRef = React.createRef();
|
||||||
|
this.menuRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener("mousedown", this.handleClickOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener("mousedown", this.handleClickOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Readonly<DropdownProps>, prevState: Readonly<DropdownState>, snapshot?: any): void {
|
||||||
|
// If the dropdown was open but now is closed, start the timeout
|
||||||
|
if (prevState.isOpen && !this.state.isOpen) {
|
||||||
|
this.timeoutId = setTimeout(() => {
|
||||||
|
if (this.menuRef.current) {
|
||||||
|
this.menuRef.current.style.display = "none";
|
||||||
|
}
|
||||||
|
}, 300); // Time is equal to the animation duration
|
||||||
|
}
|
||||||
|
// If the dropdown is now open, cancel any existing timeout and show the menu
|
||||||
|
else if (!prevState.isOpen && this.state.isOpen) {
|
||||||
|
if (this.timeoutId !== null) {
|
||||||
|
clearTimeout(this.timeoutId); // Cancel any existing timeout
|
||||||
|
this.timeoutId = null;
|
||||||
|
}
|
||||||
|
if (this.menuRef.current) {
|
||||||
|
this.menuRef.current.style.display = "inline-flex";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleClickOutside(event: MouseEvent) {
|
||||||
|
// Check if the click is outside both the wrapper and the menu
|
||||||
|
if (
|
||||||
|
this.wrapperRef.current &&
|
||||||
|
!this.wrapperRef.current.contains(event.target as Node) &&
|
||||||
|
this.menuRef.current &&
|
||||||
|
!this.menuRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
this.setState({ isOpen: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleClick() {
|
||||||
|
this.toggleDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleFocus() {
|
||||||
|
this.setState({ isTouched: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleKeyDown(event: React.KeyboardEvent) {
|
||||||
|
const { options } = this.props;
|
||||||
|
const { isOpen, highlightedIndex } = this.state;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case "Enter":
|
||||||
|
case " ":
|
||||||
|
if (isOpen) {
|
||||||
|
const option = options[highlightedIndex];
|
||||||
|
if (option) {
|
||||||
|
this.handleSelect(option.value, undefined);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.toggleDropdown();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
this.setState({ isOpen: false });
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
if (isOpen) {
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
highlightedIndex:
|
||||||
|
prevState.highlightedIndex > 0 ? prevState.highlightedIndex - 1 : options.length - 1,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
if (isOpen) {
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
highlightedIndex:
|
||||||
|
prevState.highlightedIndex < options.length - 1 ? prevState.highlightedIndex + 1 : 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Tab":
|
||||||
|
this.setState({ isOpen: false });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleSelect(value: string, event?: React.MouseEvent | React.KeyboardEvent) {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation(); // This stops the event from bubbling up to the wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!("value" in this.props)) {
|
||||||
|
this.setState({ internalValue: value });
|
||||||
|
}
|
||||||
|
onChange(value);
|
||||||
|
this.setState({ isOpen: false, isTouched: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
toggleDropdown() {
|
||||||
|
this.setState((prevState) => ({ isOpen: !prevState.isOpen, isTouched: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
calculatePosition(): React.CSSProperties {
|
||||||
|
if (this.wrapperRef.current) {
|
||||||
|
const rect = this.wrapperRef.current.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
position: "absolute",
|
||||||
|
top: `${rect.bottom + window.scrollY}px`,
|
||||||
|
left: `${rect.left + window.scrollX}px`,
|
||||||
|
width: `${rect.width}px`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { label, options, value, placeholder, decoration, className, required } = this.props;
|
||||||
|
const { isOpen, internalValue, highlightedIndex, isTouched } = this.state;
|
||||||
|
|
||||||
|
const currentValue = value ?? internalValue;
|
||||||
|
const selectedOptionLabel =
|
||||||
|
options.find((option) => option.value === currentValue)?.label || placeholder || internalValue;
|
||||||
|
|
||||||
|
// Determine if the dropdown should be marked as having an error
|
||||||
|
const isError =
|
||||||
|
required &&
|
||||||
|
(value === undefined || value === "") &&
|
||||||
|
(internalValue === undefined || internalValue === "") &&
|
||||||
|
isTouched;
|
||||||
|
|
||||||
|
// Determine if the label should float
|
||||||
|
const shouldLabelFloat = !!value || !!internalValue || !!placeholder || isOpen;
|
||||||
|
|
||||||
|
const dropdownMenu = isOpen
|
||||||
|
? ReactDOM.createPortal(
|
||||||
|
<div className={cn("wave-dropdown-menu")} ref={this.menuRef} style={this.calculatePosition()}>
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className={cn("wave-dropdown-item unselectable", {
|
||||||
|
"wave-dropdown-item-highlighted": index === highlightedIndex,
|
||||||
|
})}
|
||||||
|
onClick={(e) => this.handleSelect(option.value, e)}
|
||||||
|
onMouseEnter={() => this.setState({ highlightedIndex: index })}
|
||||||
|
onMouseLeave={() => this.setState({ highlightedIndex: -1 })}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>,
|
||||||
|
document.getElementById("app")!
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("wave-dropdown", className, {
|
||||||
|
"wave-dropdown-error": isError,
|
||||||
|
"no-label": !label,
|
||||||
|
})}
|
||||||
|
ref={this.wrapperRef}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
onFocus={this.handleFocus}
|
||||||
|
>
|
||||||
|
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
|
||||||
|
<If condition={label}>
|
||||||
|
<div
|
||||||
|
className={cn("wave-dropdown-label unselectable", {
|
||||||
|
float: shouldLabelFloat,
|
||||||
|
"offset-left": decoration?.startDecoration,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<div
|
||||||
|
className={cn("wave-dropdown-display unselectable", { "offset-left": decoration?.startDecoration })}
|
||||||
|
>
|
||||||
|
{selectedOptionLabel}
|
||||||
|
</div>
|
||||||
|
<div className={cn("wave-dropdown-arrow", { "wave-dropdown-arrow-rotate": isOpen })}>
|
||||||
|
<i className="fa-sharp fa-solid fa-chevron-down"></i>
|
||||||
|
</div>
|
||||||
|
{dropdownMenu}
|
||||||
|
{decoration?.endDecoration && <>{decoration.endDecoration}</>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Dropdown };
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { Button } from "./button";
|
||||||
|
class IconButton extends Button {
|
||||||
|
render() {
|
||||||
|
const { children, theme, variant = "solid", ...rest } = this.props;
|
||||||
|
const className = `wave-button icon-button ${theme} ${variant}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button {...rest} className={className}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IconButton;
|
||||||
|
|
||||||
|
export { IconButton };
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export { Button } from "./button";
|
||||||
|
export { Checkbox } from "./checkbox";
|
||||||
|
export { CmdStrCode } from "./cmdstrcode";
|
||||||
|
export { renderCmdText } from "./cmdtext";
|
||||||
|
export { Dropdown } from "./dropdown";
|
||||||
|
export { IconButton } from "./iconbutton";
|
||||||
|
export { InlineSettingsTextEdit } from "./inlinesettingstextedit";
|
||||||
|
export { InputDecoration } from "./inputdecoration";
|
||||||
|
export { LinkButton } from "./linkbutton";
|
||||||
|
export { Markdown } from "./markdown";
|
||||||
|
export { Modal } from "./modal";
|
||||||
|
export { NumberField } from "./numberfield";
|
||||||
|
export { PasswordField } from "./passwordfield";
|
||||||
|
export { ResizableSidebar } from "./resizablesidebar";
|
||||||
|
export { SettingsError } from "./settingserror";
|
||||||
|
export { ShowWaveShellInstallPrompt } from "./showwaveshellinstallprompt";
|
||||||
|
export { Status } from "./status";
|
||||||
|
export { TextField } from "./textfield";
|
||||||
|
export { Toggle } from "./toggle";
|
||||||
|
export { Tooltip } from "./tooltip";
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@import "../../../app/common/themes/themes.less";
|
||||||
|
|
||||||
|
.inline-edit {
|
||||||
|
.icon {
|
||||||
|
display: inline;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-left: 1em;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.edit-not-active {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
i.fa-pen {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.edit-active {
|
||||||
|
input.input {
|
||||||
|
padding: 0;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as mobxReact from "mobx-react";
|
||||||
|
import * as mobx from "mobx";
|
||||||
|
import { boundMethod } from "autobind-decorator";
|
||||||
|
import cn from "classnames";
|
||||||
|
import { If } from "tsx-control-statements/components";
|
||||||
|
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../../util/keyutil";
|
||||||
|
|
||||||
|
import "./inlinesettingstextedit.less";
|
||||||
|
|
||||||
|
type OV<V> = mobx.IObservableValue<V>;
|
||||||
|
|
||||||
|
@mobxReact.observer
|
||||||
|
class InlineSettingsTextEdit extends React.Component<
|
||||||
|
{
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (val: string) => void;
|
||||||
|
maxLength: number;
|
||||||
|
placeholder: string;
|
||||||
|
showIcon?: boolean;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
> {
|
||||||
|
isEditing: OV<boolean> = mobx.observable.box(false, { name: "inlineedit-isEditing" });
|
||||||
|
tempText: OV<string>;
|
||||||
|
shouldFocus: boolean = false;
|
||||||
|
inputRef: React.RefObject<any> = React.createRef();
|
||||||
|
|
||||||
|
componentDidUpdate(): void {
|
||||||
|
if (this.shouldFocus) {
|
||||||
|
this.shouldFocus = false;
|
||||||
|
if (this.inputRef.current != null) {
|
||||||
|
this.inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleChangeText(e: any): void {
|
||||||
|
mobx.action(() => {
|
||||||
|
this.tempText.set(e.target.value);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
confirmChange(): void {
|
||||||
|
mobx.action(() => {
|
||||||
|
let newText = this.tempText.get();
|
||||||
|
this.isEditing.set(false);
|
||||||
|
this.tempText = null;
|
||||||
|
this.props.onChange(newText);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
cancelChange(): void {
|
||||||
|
mobx.action(() => {
|
||||||
|
this.isEditing.set(false);
|
||||||
|
this.tempText = null;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
handleKeyDown(e: any): void {
|
||||||
|
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||||
|
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.confirmChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.cancelChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
clickEdit(): void {
|
||||||
|
mobx.action(() => {
|
||||||
|
this.isEditing.set(true);
|
||||||
|
this.shouldFocus = true;
|
||||||
|
this.tempText = mobx.observable.box(this.props.value, { name: "inlineedit-tempText" });
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.isEditing.get()) {
|
||||||
|
return (
|
||||||
|
<div className={cn("settings-input inline-edit", "edit-active")}>
|
||||||
|
<div className="field has-addons">
|
||||||
|
<div className="control">
|
||||||
|
<input
|
||||||
|
ref={this.inputRef}
|
||||||
|
className="input"
|
||||||
|
type="text"
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
onChange={this.handleChangeText}
|
||||||
|
value={this.tempText.get()}
|
||||||
|
maxLength={this.props.maxLength}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="control">
|
||||||
|
<div
|
||||||
|
onClick={this.cancelChange}
|
||||||
|
title="Cancel (Esc)"
|
||||||
|
className="button is-prompt-danger is-outlined is-small"
|
||||||
|
>
|
||||||
|
<span className="icon is-small">
|
||||||
|
<i className="fa-sharp fa-solid fa-xmark" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="control">
|
||||||
|
<div
|
||||||
|
onClick={this.confirmChange}
|
||||||
|
title="Confirm (Enter)"
|
||||||
|
className="button is-wave-green is-outlined is-small"
|
||||||
|
>
|
||||||
|
<span className="icon is-small">
|
||||||
|
<i className="fa-sharp fa-solid fa-check" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div onClick={this.clickEdit} className={cn("settings-input inline-edit", "edit-not-active")}>
|
||||||
|
{this.props.text}
|
||||||
|
<If condition={this.props.showIcon}>
|
||||||
|
<i className="fa-sharp fa-solid fa-pen" />
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { InlineSettingsTextEdit };
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@import "../../../app/common/themes/themes.less";
|
||||||
|
|
||||||
|
.wave-input-decoration {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-input-decoration.start-position {
|
||||||
|
margin: 0 4px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-input-decoration.end-position {
|
||||||
|
margin: 0 16px 0 8px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// 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 "./inputdecoration.less";
|
||||||
|
|
||||||
|
interface InputDecorationProps {
|
||||||
|
position?: "start" | "end";
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mobxReact.observer
|
||||||
|
class InputDecoration extends React.Component<InputDecorationProps, {}> {
|
||||||
|
render() {
|
||||||
|
const { children, position = "end" } = this.props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("wave-input-decoration", {
|
||||||
|
"start-position": position === "start",
|
||||||
|
"end-position": position === "end",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { InputDecoration };
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2023, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import cn from "classnames";
|
||||||
|
import { ButtonProps } from "./button";
|
||||||
|
|
||||||
|
interface LinkButtonProps extends ButtonProps {
|
||||||
|
href: string;
|
||||||
|
rel?: string;
|
||||||
|
target?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LinkButton extends React.Component<LinkButtonProps> {
|
||||||
|
render() {
|
||||||
|
const { leftIcon, rightIcon, children, className, ...rest } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a {...rest} className={cn(`wave-button link-button`, className)}>
|
||||||
|
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||||
|
{children}
|
||||||
|
{rightIcon && <span className="icon-right">{rightIcon}</span>}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LinkButton };
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user