diff --git a/src/app/common/common.tsx b/src/app/common/common.tsx deleted file mode 100644 index 578eb6090..000000000 --- a/src/app/common/common.tsx +++ /dev/null @@ -1,1452 +0,0 @@ -// 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 ReactMarkdown from "react-markdown"; -import remarkGfm from "remark-gfm"; -import cn from "classnames"; -import { If } from "tsx-control-statements/components"; -import { RemoteType } from "../../types/types"; -import ReactDOM from "react-dom"; -import { GlobalModel, GlobalCommandRunner } from "../../model/model"; -import * as appconst from "../appconst"; -import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil"; -import { MagicLayout } from "../magiclayout"; - -import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg"; -import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg"; -import { ReactComponent as CircleIcon } from "../assets/icons/circle.svg"; -import { ReactComponent as KeyIcon } from "../assets/icons/key.svg"; -import { ReactComponent as RotateIcon } from "../assets/icons/rotate_left.svg"; -import { ReactComponent as CircleInfoIcon } from "../assets/icons/circle_info.svg"; - -import "./common.less"; - -type OV = mobx.IObservableValue; - -function renderCmdText(text: string): any { - return ⌘{text}; -} - -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 ( -
- -
-
copied
-
-
-
- -
-
- {cmdstr} -
-
-
- -
-
-
- ); - } -} - -class Toggle extends React.Component<{ checked: boolean; onChange: (value: boolean) => void }, {}> { - @boundMethod - handleChange(e: any): void { - let { onChange } = this.props; - if (onChange != null) { - onChange(e.target.checked); - } - } - - render() { - return ( - - ); - } -} - -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 ( -
- - -
- ); - } -} - -interface InputDecorationProps { - position?: "start" | "end"; - children: React.ReactNode; -} - -@mobxReact.observer -class InputDecoration extends React.Component { - render() { - const { children, position = "end" } = this.props; - return ( -
- {children} -
- ); - } -} - -interface TooltipProps { - message: React.ReactNode; - icon?: React.ReactNode; // Optional icon property - children: React.ReactNode; - className?: string; -} - -interface TooltipState { - isVisible: boolean; -} - -@mobxReact.observer -class Tooltip extends React.Component { - iconRef: React.RefObject; - - constructor(props: TooltipProps) { - super(props); - this.state = { - isVisible: false, - }; - this.iconRef = React.createRef(); - } - - @boundMethod - showBubble() { - this.setState({ isVisible: true }); - } - - @boundMethod - hideBubble() { - this.setState({ isVisible: false }); - } - - @boundMethod - calculatePosition() { - // Get the position of the icon element - const iconElement = this.iconRef.current; - if (iconElement) { - const rect = iconElement.getBoundingClientRect(); - return { - top: `${rect.bottom + window.scrollY - 29}px`, - left: `${rect.left + window.scrollX + rect.width / 2 - 17.5}px`, - }; - } - return {}; - } - - @boundMethod - renderBubble() { - if (!this.state.isVisible) return null; - - const style = this.calculatePosition(); - - return ReactDOM.createPortal( -
- {this.props.icon &&
{this.props.icon}
} -
{this.props.message}
-
, - document.getElementById("app")! - ); - } - - render() { - return ( -
- {this.props.children} - {this.renderBubble()} -
- ); - } -} - -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 { - 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 ( - - ); - } -} - -class IconButton extends Button { - render() { - const { children, theme, variant = "solid", ...rest } = this.props; - const className = `wave-button icon-button ${theme} ${variant}`; - - return ( - - ); - } -} - -export default IconButton; - -interface LinkButtonProps extends ButtonProps { - href: string; - rel?: string; - target?: string; -} - -class LinkButton extends React.Component { - render() { - const { leftIcon, rightIcon, children, className, ...rest } = this.props; - - return ( - - {leftIcon && {leftIcon}} - {children} - {rightIcon && {rightIcon}} - - ); - } -} - -interface StatusProps { - status: "green" | "red" | "gray" | "yellow"; - text: string; -} - -class Status extends React.Component { - @boundMethod - renderDot() { - const { status } = this.props; - - return
; - } - - render() { - const { text } = this.props; - - return ( -
- {this.renderDot()} - {text} -
- ); - } -} - -interface TextFieldDecorationProps { - startDecoration?: React.ReactNode; - endDecoration?: React.ReactNode; -} -interface TextFieldProps { - label?: string; - value?: string; - className?: string; - onChange?: (value: string) => void; - placeholder?: string; - defaultValue?: string; - decoration?: TextFieldDecorationProps; - required?: boolean; - maxLength?: number; - autoFocus?: boolean; - disabled?: boolean; -} - -interface TextFieldState { - focused: boolean; - internalValue: string; - error: boolean; - showHelpText: boolean; - hasContent: boolean; -} - -class TextField extends React.Component { - inputRef: React.RefObject; - state: TextFieldState; - - constructor(props: TextFieldProps) { - super(props); - const hasInitialContent = Boolean(props.value || props.defaultValue); - this.state = { - focused: false, - hasContent: hasInitialContent, - internalValue: props.defaultValue || "", - error: false, - showHelpText: false, - }; - this.inputRef = React.createRef(); - } - - componentDidUpdate(prevProps: TextFieldProps) { - // Only update the focus state if using as controlled - if (this.props.value !== undefined && this.props.value !== prevProps.value) { - this.setState({ focused: Boolean(this.props.value) }); - } - } - - // Method to handle focus at the component level - @boundMethod - handleComponentFocus() { - if (this.inputRef.current && !this.inputRef.current.contains(document.activeElement)) { - this.inputRef.current.focus(); - } - } - - // Method to handle blur at the component level - @boundMethod - handleComponentBlur() { - if (this.inputRef.current?.contains(document.activeElement)) { - this.inputRef.current.blur(); - } - } - - @boundMethod - handleFocus() { - this.setState({ focused: true }); - } - - @boundMethod - handleBlur() { - const { required } = this.props; - if (this.inputRef.current) { - const value = this.inputRef.current.value; - if (required && !value) { - this.setState({ error: true, focused: false }); - } else { - this.setState({ error: false, focused: false }); - } - } - } - - @boundMethod - handleHelpTextClick() { - this.setState((prevState) => ({ showHelpText: !prevState.showHelpText })); - } - - @boundMethod - handleInputChange(e: React.ChangeEvent) { - const { required, onChange } = this.props; - const inputValue = e.target.value; - - // Check if value is empty and the field is required - if (required && !inputValue) { - this.setState({ error: true, hasContent: false }); - } else { - this.setState({ error: false, hasContent: Boolean(inputValue) }); - } - - // Update the internal state for uncontrolled version - if (this.props.value === undefined) { - this.setState({ internalValue: inputValue }); - } - - onChange && onChange(inputValue); - } - - render() { - const { label, value, placeholder, decoration, className, maxLength, autoFocus, disabled } = this.props; - const { focused, internalValue, error } = this.state; - - // Decide if the input should behave as controlled or uncontrolled - const inputValue = value ?? internalValue; - - return ( -
- {decoration?.startDecoration && <>{decoration.startDecoration}} -
- - - - -
- {decoration?.endDecoration && <>{decoration.endDecoration}} -
- ); - } -} - -class NumberField extends TextField { - @boundMethod - handleInputChange(e: React.ChangeEvent) { - const { required, onChange } = this.props; - const inputValue = e.target.value; - - // Allow only numeric input - if (inputValue === "" || /^\d*$/.test(inputValue)) { - // Update the internal state only if the component is not controlled. - if (this.props.value === undefined) { - const isError = required ? inputValue.trim() === "" : false; - - this.setState({ - internalValue: inputValue, - error: isError, - hasContent: Boolean(inputValue), - }); - } - - onChange && onChange(inputValue); - } - } - - render() { - // Use the render method from TextField but add the onKeyDown handler - const renderedTextField = super.render(); - return React.cloneElement(renderedTextField); - } -} - -interface PasswordFieldState extends TextFieldState { - passwordVisible: boolean; -} - -@mobxReact.observer -class PasswordField extends TextField { - state: PasswordFieldState; - - constructor(props) { - super(props); - this.state = { - ...this.state, - passwordVisible: false, - }; - } - - @boundMethod - togglePasswordVisibility() { - //@ts-ignore - this.setState((prevState) => ({ - //@ts-ignore - passwordVisible: !prevState.passwordVisible, - })); - } - - @boundMethod - handleInputChange(e: React.ChangeEvent) { - // Call the parent handleInputChange method - super.handleInputChange(e); - } - - render() { - const { decoration, className, placeholder, maxLength, label } = this.props; - const { focused, internalValue, error, passwordVisible } = this.state; - const inputValue = this.props.value ?? internalValue; - - // The input should always receive the real value - const inputProps = { - className: cn("wave-textfield-inner-input", { "offset-left": decoration?.startDecoration }), - ref: this.inputRef, - id: label, - value: inputValue, // Always use the real value here - onChange: this.handleInputChange, - onFocus: this.handleFocus, - onBlur: this.handleBlur, - placeholder: placeholder, - maxLength: maxLength, - }; - - return ( -
- {decoration?.startDecoration && <>{decoration.startDecoration}} -
- - - - - - - -
- - - - - - -
-
- {decoration?.endDecoration && <>{decoration.endDecoration}} -
- ); - } -} - -@mobxReact.observer -class RemoteStatusLight extends React.Component<{ remote: RemoteType }, {}> { - render() { - let remote = this.props.remote; - let status = "error"; - let wfp = false; - if (remote != null) { - status = remote.status; - wfp = remote.waitingforpassword; - } - if (status == "connecting") { - if (wfp) return ; - else return ; - } - return ; - } -} - -@mobxReact.observer -class InlineSettingsTextEdit extends React.Component< - { - text: string; - value: string; - onChange: (val: string) => void; - maxLength: number; - placeholder: string; - showIcon?: boolean; - }, - {} -> { - isEditing: OV = mobx.observable.box(false, { name: "inlineedit-isEditing" }); - tempText: OV; - shouldFocus: boolean = false; - inputRef: React.RefObject = 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 ( -
-
-
- -
-
-
- - - -
-
-
-
- - - -
-
-
-
- ); - } else { - return ( -
- {this.props.text} - - - -
- ); - } - } -} - -@mobxReact.observer -class InfoMessage extends React.Component<{ width: number; children: React.ReactNode }> { - render() { - return ( -
-
- -
-
-
- -
-
{this.props.children}
-
-
- ); - } -} - -function LinkRenderer(props: any): any { - let newUrl = "https://extern?" + encodeURIComponent(props.href); - return ( - - {props.children} - - ); -} - -function HeaderRenderer(props: any, hnum: number): any { - return
{props.children}
; -} - -function CodeRenderer(props: any): any { - return {props.children}; -} - -@mobxReact.observer -class CodeBlockMarkdown extends React.Component<{ children: React.ReactNode; codeSelectSelectedIndex?: number }, {}> { - blockIndex: number; - blockRef: React.RefObject; - - constructor(props) { - super(props); - this.blockRef = React.createRef(); - this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef); - } - - render() { - let clickHandler: (e: React.MouseEvent, blockIndex: number) => void; - let inputModel = GlobalModel.inputModel; - clickHandler = (e: React.MouseEvent, blockIndex: number) => { - inputModel.setCodeSelectSelectedCodeBlock(blockIndex); - }; - let selected = this.blockIndex == this.props.codeSelectSelectedIndex; - return ( -
 clickHandler(event, this.blockIndex)}
-            >
-                {this.props.children}
-            
- ); - } -} - -@mobxReact.observer -class Markdown extends React.Component< - { text: string; style?: any; extraClassName?: string; codeSelect?: boolean }, - {} -> { - CodeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number): any { - if (codeSelect) { - return {props.children}; - } else { - const clickHandler = (e: React.MouseEvent) => { - let blockText = (e.target as HTMLElement).innerText; - if (blockText) { - blockText = blockText.replace(/\n$/, ""); // remove trailing newline - navigator.clipboard.writeText(blockText); - } - }; - return
 clickHandler(event)}>{props.children}
; - } - } - - render() { - let text = this.props.text; - let codeSelect = this.props.codeSelect; - let curCodeSelectIndex = GlobalModel.inputModel.getCodeSelectSelectedIndex(); - let markdownComponents = { - a: LinkRenderer, - h1: (props) => HeaderRenderer(props, 1), - h2: (props) => HeaderRenderer(props, 2), - h3: (props) => HeaderRenderer(props, 3), - h4: (props) => HeaderRenderer(props, 4), - h5: (props) => HeaderRenderer(props, 5), - h6: (props) => HeaderRenderer(props, 6), - code: (props) => CodeRenderer(props), - pre: (props) => this.CodeBlockRenderer(props, codeSelect, curCodeSelectIndex), - }; - return ( -
- - {text} - -
- ); - } -} - -@mobxReact.observer -class SettingsError extends React.Component<{ errorMessage: OV }, {}> { - @boundMethod - dismissError(): void { - mobx.action(() => { - this.props.errorMessage.set(null); - })(); - } - - render() { - if (this.props.errorMessage.get() == null) { - return null; - } - return ( -
-
Error: {this.props.errorMessage.get()}
-
-
- -
-
- ); - } -} - -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 { - wrapperRef: React.RefObject; - menuRef: React.RefObject; - 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, prevState: Readonly, 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( -
- {options.map((option, index) => ( -
this.handleSelect(option.value, e)} - onMouseEnter={() => this.setState({ highlightedIndex: index })} - onMouseLeave={() => this.setState({ highlightedIndex: -1 })} - > - {option.label} -
- ))} -
, - document.getElementById("app")! - ) - : null; - - return ( -
- {decoration?.startDecoration && <>{decoration.startDecoration}} - -
- {label} -
-
-
- {selectedOptionLabel} -
-
- -
- {dropdownMenu} - {decoration?.endDecoration && <>{decoration.endDecoration}} -
- ); - } -} - -interface ModalHeaderProps { - onClose?: () => void; - title: string; -} - -const ModalHeader: React.FC = ({ onClose, title }) => ( -
- {
{title}
} - - - - - -
-); - -interface ModalFooterProps { - onCancel?: () => void; - onOk?: () => void; - cancelLabel?: string; - okLabel?: string; -} - -const ModalFooter: React.FC = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }) => ( -
- {onCancel && ( - - )} - {onOk && } -
-); - -interface ModalProps { - className?: string; - children?: React.ReactNode; - onClickBackdrop?: () => void; -} - -class Modal extends React.Component { - static Header = ModalHeader; - static Footer = ModalFooter; - - renderBackdrop(onClick: (() => void) | undefined) { - return
; - } - - renderModal() { - const { className, children } = this.props; - - return ( -
- {this.renderBackdrop(this.props.onClickBackdrop)} -
-
{children}
-
-
- ); - } - - render() { - return ReactDOM.createPortal(this.renderModal(), document.getElementById("app")); - } -} - -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). - `; - message = message.trim(); - let prtn = GlobalModel.showAlert({ - message: message, - confirm: true, - markdown: true, - confirmflag: appconst.ConfirmKey_HideShellPrompt, - }); - prtn.then((confirm) => { - if (!confirm) { - return; - } - if (callbackFn) { - callbackFn(); - } - }); -} - -interface ResizableSidebarProps { - parentRef: React.RefObject; - position: "left" | "right"; - enableSnap?: boolean; - className?: string; - children?: (toggleCollapsed: () => void) => React.ReactNode; - toggleCollapse?: () => void; -} - -@mobxReact.observer -class ResizableSidebar extends React.Component { - resizeStartWidth: number = 0; - startX: number = 0; - prevDelta: number = 0; - prevDragDirection: string = null; - disposeReaction: any; - - @boundMethod - startResizing(event: React.MouseEvent) { - event.preventDefault(); - - const { parentRef, position } = this.props; - const parentRect = parentRef.current?.getBoundingClientRect(); - - if (!parentRect) return; - - if (position === "right") { - this.startX = parentRect.right - event.clientX; - } else { - this.startX = event.clientX - parentRect.left; - } - - const mainSidebarModel = GlobalModel.mainSidebarModel; - const collapsed = mainSidebarModel.getCollapsed(); - - this.resizeStartWidth = mainSidebarModel.getWidth(); - document.addEventListener("mousemove", this.onMouseMove); - document.addEventListener("mouseup", this.stopResizing); - - document.body.style.cursor = "col-resize"; - mobx.action(() => { - mainSidebarModel.setTempWidthAndTempCollapsed(this.resizeStartWidth, collapsed); - mainSidebarModel.isDragging.set(true); - })(); - } - - @boundMethod - onMouseMove(event: MouseEvent) { - event.preventDefault(); - - const { parentRef, enableSnap, position } = this.props; - const parentRect = parentRef.current?.getBoundingClientRect(); - const mainSidebarModel = GlobalModel.mainSidebarModel; - - if (!mainSidebarModel.isDragging.get() || !parentRect) return; - - let delta: number, newWidth: number; - - if (position === "right") { - delta = parentRect.right - event.clientX - this.startX; - } else { - delta = event.clientX - parentRect.left - this.startX; - } - - newWidth = this.resizeStartWidth + delta; - - if (enableSnap) { - const minWidth = MagicLayout.MainSidebarMinWidth; - const snapPoint = minWidth + MagicLayout.MainSidebarSnapThreshold; - const dragResistance = MagicLayout.MainSidebarDragResistance; - let dragDirection: string; - - if (delta - this.prevDelta > 0) { - dragDirection = "+"; - } else if (delta - this.prevDelta == 0) { - if (this.prevDragDirection == "+") { - dragDirection = "+"; - } else { - dragDirection = "-"; - } - } else { - dragDirection = "-"; - } - - this.prevDelta = delta; - this.prevDragDirection = dragDirection; - - if (newWidth - dragResistance > minWidth && newWidth < snapPoint && dragDirection == "+") { - newWidth = snapPoint; - mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false); - } else if (newWidth + dragResistance < snapPoint && dragDirection == "-") { - newWidth = minWidth; - mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true); - } else if (newWidth > snapPoint) { - mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false); - } - } else { - if (newWidth <= MagicLayout.MainSidebarMinWidth) { - mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true); - } else { - mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false); - } - } - } - - @boundMethod - stopResizing() { - let mainSidebarModel = GlobalModel.mainSidebarModel; - - GlobalCommandRunner.clientSetSidebar( - mainSidebarModel.tempWidth.get(), - mainSidebarModel.tempCollapsed.get() - ).finally(() => { - mobx.action(() => { - mainSidebarModel.isDragging.set(false); - })(); - }); - - document.removeEventListener("mousemove", this.onMouseMove); - document.removeEventListener("mouseup", this.stopResizing); - document.body.style.cursor = ""; - } - - @boundMethod - toggleCollapsed() { - const mainSidebarModel = GlobalModel.mainSidebarModel; - - const tempCollapsed = mainSidebarModel.getCollapsed(); - const width = mainSidebarModel.getWidth(true); - mainSidebarModel.setTempWidthAndTempCollapsed(width, !tempCollapsed); - GlobalCommandRunner.clientSetSidebar(width, !tempCollapsed); - } - - render() { - const { className, children } = this.props; - const mainSidebarModel = GlobalModel.mainSidebarModel; - const width = mainSidebarModel.getWidth(); - const isCollapsed = mainSidebarModel.getCollapsed(); - - return ( -
-
{children(this.toggleCollapsed)}
-
-
- ); - } -} - -export { - CmdStrCode, - Toggle, - Checkbox, - renderCmdText, - RemoteStatusLight, - InlineSettingsTextEdit, - InfoMessage, - Markdown, - SettingsError, - Dropdown, - TextField, - InputDecoration, - NumberField, - PasswordField, - Tooltip, - Button, - IconButton, - LinkButton, - Status, - Modal, - ResizableSidebar, - ShowWaveShellInstallPrompt, -}; diff --git a/src/app/common/common.less b/src/app/common/elements/button.less similarity index 100% rename from src/app/common/common.less rename to src/app/common/elements/button.less diff --git a/src/app/common/elements/button.tsx b/src/app/common/elements/button.tsx new file mode 100644 index 000000000..cdd218250 --- /dev/null +++ b/src/app/common/elements/button.tsx @@ -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 { + 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 ( + + ); + } +} + +export { Button }; +export type { ButtonProps }; diff --git a/src/app/common/elements/checkbox.less b/src/app/common/elements/checkbox.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/checkbox.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/checkbox.tsx b/src/app/common/elements/checkbox.tsx new file mode 100644 index 000000000..57ece9c5c --- /dev/null +++ b/src/app/common/elements/checkbox.tsx @@ -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 ( +
+ + +
+ ); + } +} + +export { Checkbox }; diff --git a/src/app/common/elements/cmdstrcode.less b/src/app/common/elements/cmdstrcode.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/cmdstrcode.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/cmdstrcode.tsx b/src/app/common/elements/cmdstrcode.tsx new file mode 100644 index 000000000..5d32450d5 --- /dev/null +++ b/src/app/common/elements/cmdstrcode.tsx @@ -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 ( +
+ +
+
copied
+
+
+
+ +
+
+ {cmdstr} +
+
+
+ +
+
+
+ ); + } +} + +export { CmdStrCode }; diff --git a/src/app/common/elements/cmdtext.less b/src/app/common/elements/cmdtext.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/cmdtext.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/cmdtext.tsx b/src/app/common/elements/cmdtext.tsx new file mode 100644 index 000000000..f16841182 --- /dev/null +++ b/src/app/common/elements/cmdtext.tsx @@ -0,0 +1,13 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobx from "mobx"; + +import "./cmdtext.less"; + +function renderCmdText(text: string): any { + return ⌘{text}; +} + +export { renderCmdText }; diff --git a/src/app/common/elements/dropdown.less b/src/app/common/elements/dropdown.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/dropdown.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/dropdown.tsx b/src/app/common/elements/dropdown.tsx new file mode 100644 index 000000000..6a3034c2a --- /dev/null +++ b/src/app/common/elements/dropdown.tsx @@ -0,0 +1,262 @@ +// 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 ReactDOM from "react-dom"; + +import "./common.less"; + +type OV = mobx.IObservableValue; + +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 { + wrapperRef: React.RefObject; + menuRef: React.RefObject; + 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, prevState: Readonly, 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( +
+ {options.map((option, index) => ( +
this.handleSelect(option.value, e)} + onMouseEnter={() => this.setState({ highlightedIndex: index })} + onMouseLeave={() => this.setState({ highlightedIndex: -1 })} + > + {option.label} +
+ ))} +
, + document.getElementById("app")! + ) + : null; + + return ( +
+ {decoration?.startDecoration && <>{decoration.startDecoration}} + +
+ {label} +
+
+
+ {selectedOptionLabel} +
+
+ +
+ {dropdownMenu} + {decoration?.endDecoration && <>{decoration.endDecoration}} +
+ ); + } +} + +export { Dropdown }; diff --git a/src/app/common/elements/iconbutton.less b/src/app/common/elements/iconbutton.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/iconbutton.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/iconbutton.tsx b/src/app/common/elements/iconbutton.tsx new file mode 100644 index 000000000..791eec753 --- /dev/null +++ b/src/app/common/elements/iconbutton.tsx @@ -0,0 +1,25 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobx from "mobx"; +import { Button } from "./button"; + +import "./iconbutton.less"; + +class IconButton extends Button { + render() { + const { children, theme, variant = "solid", ...rest } = this.props; + const className = `wave-button icon-button ${theme} ${variant}`; + + return ( + + ); + } +} + +export default IconButton; + +export { IconButton }; diff --git a/src/app/common/elements/index.tsx b/src/app/common/elements/index.tsx new file mode 100644 index 000000000..cfd2f529f --- /dev/null +++ b/src/app/common/elements/index.tsx @@ -0,0 +1,3 @@ +export { CmdStrCode } from "./cmdstrcode"; +export { renderCmdText } from "./cmdtext"; +export { Toggle } from "./toggle"; diff --git a/src/app/common/elements/infomessage.less b/src/app/common/elements/infomessage.less new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/common/elements/infomessage.tsx b/src/app/common/elements/infomessage.tsx new file mode 100644 index 000000000..6e7d93e27 --- /dev/null +++ b/src/app/common/elements/infomessage.tsx @@ -0,0 +1,31 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import { ReactComponent as CircleInfoIcon } from "../assets/icons/circle_info.svg"; + +import "./infomessage.less"; + +// NOTE: this deprecated component. Use Tooltip instead. + +@mobxReact.observer +class InfoMessage extends React.Component<{ width: number; children: React.ReactNode }> { + render() { + return ( +
+
+ +
+
+
+ +
+
{this.props.children}
+
+
+ ); + } +} + +export { InfoMessage }; diff --git a/src/app/common/elements/inlinetextedit.less b/src/app/common/elements/inlinetextedit.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/inlinetextedit.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/inlinetextedit.tsx b/src/app/common/elements/inlinetextedit.tsx new file mode 100644 index 000000000..6fec6176c --- /dev/null +++ b/src/app/common/elements/inlinetextedit.tsx @@ -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 "./inlinetextedit.less"; + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class InlineSettingsTextEdit extends React.Component< + { + text: string; + value: string; + onChange: (val: string) => void; + maxLength: number; + placeholder: string; + showIcon?: boolean; + }, + {} +> { + isEditing: OV = mobx.observable.box(false, { name: "inlineedit-isEditing" }); + tempText: OV; + shouldFocus: boolean = false; + inputRef: React.RefObject = 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 ( +
+
+
+ +
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+ ); + } else { + return ( +
+ {this.props.text} + + + +
+ ); + } + } +} + +export { InlineSettingsTextEdit }; diff --git a/src/app/common/elements/inputdecoraction.less b/src/app/common/elements/inputdecoraction.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/inputdecoraction.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/inputdecoraction.tsx b/src/app/common/elements/inputdecoraction.tsx new file mode 100644 index 000000000..d74b1a417 --- /dev/null +++ b/src/app/common/elements/inputdecoraction.tsx @@ -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 { + render() { + const { children, position = "end" } = this.props; + return ( +
+ {children} +
+ ); + } +} + +export { InputDecoration }; diff --git a/src/app/common/elements/linkbutton.less b/src/app/common/elements/linkbutton.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/linkbutton.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/linkbutton.tsx b/src/app/common/elements/linkbutton.tsx new file mode 100644 index 000000000..edc148d18 --- /dev/null +++ b/src/app/common/elements/linkbutton.tsx @@ -0,0 +1,30 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import cn from "classnames"; +import { ButtonProps } from "./button"; + +import "./linkbutton.less"; + +interface LinkButtonProps extends ButtonProps { + href: string; + rel?: string; + target?: string; +} + +class LinkButton extends React.Component { + render() { + const { leftIcon, rightIcon, children, className, ...rest } = this.props; + + return ( + + {leftIcon && {leftIcon}} + {children} + {rightIcon && {rightIcon}} + + ); + } +} + +export { LinkButton }; diff --git a/src/app/common/elements/markdown.less b/src/app/common/elements/markdown.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/markdown.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/markdown.tsx b/src/app/common/elements/markdown.tsx new file mode 100644 index 000000000..66804d2c6 --- /dev/null +++ b/src/app/common/elements/markdown.tsx @@ -0,0 +1,105 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import cn from "classnames"; +import { GlobalModel } from "../../../model/model"; + +import "./markdown.less"; + +function LinkRenderer(props: any): any { + let newUrl = "https://extern?" + encodeURIComponent(props.href); + return ( + + {props.children} + + ); +} + +function HeaderRenderer(props: any, hnum: number): any { + return
{props.children}
; +} + +function CodeRenderer(props: any): any { + return {props.children}; +} + +@mobxReact.observer +class CodeBlockMarkdown extends React.Component<{ children: React.ReactNode; codeSelectSelectedIndex?: number }, {}> { + blockIndex: number; + blockRef: React.RefObject; + + constructor(props) { + super(props); + this.blockRef = React.createRef(); + this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef); + } + + render() { + let clickHandler: (e: React.MouseEvent, blockIndex: number) => void; + let inputModel = GlobalModel.inputModel; + clickHandler = (e: React.MouseEvent, blockIndex: number) => { + inputModel.setCodeSelectSelectedCodeBlock(blockIndex); + }; + let selected = this.blockIndex == this.props.codeSelectSelectedIndex; + return ( +
 clickHandler(event, this.blockIndex)}
+            >
+                {this.props.children}
+            
+ ); + } +} + +@mobxReact.observer +class Markdown extends React.Component< + { text: string; style?: any; extraClassName?: string; codeSelect?: boolean }, + {} +> { + CodeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number): any { + if (codeSelect) { + return {props.children}; + } else { + const clickHandler = (e: React.MouseEvent) => { + let blockText = (e.target as HTMLElement).innerText; + if (blockText) { + blockText = blockText.replace(/\n$/, ""); // remove trailing newline + navigator.clipboard.writeText(blockText); + } + }; + return
 clickHandler(event)}>{props.children}
; + } + } + + render() { + let text = this.props.text; + let codeSelect = this.props.codeSelect; + let curCodeSelectIndex = GlobalModel.inputModel.getCodeSelectSelectedIndex(); + let markdownComponents = { + a: LinkRenderer, + h1: (props) => HeaderRenderer(props, 1), + h2: (props) => HeaderRenderer(props, 2), + h3: (props) => HeaderRenderer(props, 3), + h4: (props) => HeaderRenderer(props, 4), + h5: (props) => HeaderRenderer(props, 5), + h6: (props) => HeaderRenderer(props, 6), + code: (props) => CodeRenderer(props), + pre: (props) => this.CodeBlockRenderer(props, codeSelect, curCodeSelectIndex), + }; + return ( +
+ + {text} + +
+ ); + } +} + +export { Markdown }; diff --git a/src/app/common/elements/modal.less b/src/app/common/elements/modal.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/modal.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/modal.tsx b/src/app/common/elements/modal.tsx new file mode 100644 index 000000000..00a62445d --- /dev/null +++ b/src/app/common/elements/modal.tsx @@ -0,0 +1,81 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobx from "mobx"; +import { If } from "tsx-control-statements/components"; +import ReactDOM from "react-dom"; +import { Button } from "./button"; +import { IconButton } from "./iconbutton"; + +import "./common.less"; + +type OV = mobx.IObservableValue; + +interface ModalHeaderProps { + onClose?: () => void; + title: string; +} + +const ModalHeader: React.FC = ({ onClose, title }) => ( +
+ {
{title}
} + + + + + +
+); + +interface ModalFooterProps { + onCancel?: () => void; + onOk?: () => void; + cancelLabel?: string; + okLabel?: string; +} + +const ModalFooter: React.FC = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }) => ( +
+ {onCancel && ( + + )} + {onOk && } +
+); + +interface ModalProps { + className?: string; + children?: React.ReactNode; + onClickBackdrop?: () => void; +} + +class Modal extends React.Component { + static Header = ModalHeader; + static Footer = ModalFooter; + + renderBackdrop(onClick: (() => void) | undefined) { + return
; + } + + renderModal() { + const { className, children } = this.props; + + return ( +
+ {this.renderBackdrop(this.props.onClickBackdrop)} +
+
{children}
+
+
+ ); + } + + render() { + return ReactDOM.createPortal(this.renderModal(), document.getElementById("app")); + } +} + +export { Modal }; diff --git a/src/app/common/elements/numberfield.less b/src/app/common/elements/numberfield.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/numberfield.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/numberfield.tsx b/src/app/common/elements/numberfield.tsx new file mode 100644 index 000000000..609c19b9b --- /dev/null +++ b/src/app/common/elements/numberfield.tsx @@ -0,0 +1,44 @@ +// 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 { TextField, TextFieldState } from "./textfield"; + +import "./numberfield.less"; + +class NumberField extends TextField { + @boundMethod + handleInputChange(e: React.ChangeEvent) { + const { required, onChange } = this.props; + const inputValue = e.target.value; + + // Allow only numeric input + if (inputValue === "" || /^\d*$/.test(inputValue)) { + // Update the internal state only if the component is not controlled. + if (this.props.value === undefined) { + const isError = required ? inputValue.trim() === "" : false; + + this.setState({ + internalValue: inputValue, + error: isError, + hasContent: Boolean(inputValue), + }); + } + + onChange && onChange(inputValue); + } + } + + render() { + // Use the render method from TextField but add the onKeyDown handler + const renderedTextField = super.render(); + return React.cloneElement(renderedTextField); + } +} + +export { NumberField }; diff --git a/src/app/common/elements/passwordfield.less b/src/app/common/elements/passwordfield.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/passwordfield.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/passwordfield.tsx b/src/app/common/elements/passwordfield.tsx new file mode 100644 index 000000000..b2781e03b --- /dev/null +++ b/src/app/common/elements/passwordfield.tsx @@ -0,0 +1,100 @@ +// 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 { TextFieldState, TextField } from "./textfield"; + +import "./passwordfield.less"; + +interface PasswordFieldState extends TextFieldState { + passwordVisible: boolean; +} + +@mobxReact.observer +class PasswordField extends TextField { + state: PasswordFieldState; + + constructor(props) { + super(props); + this.state = { + ...this.state, + passwordVisible: false, + }; + } + + @boundMethod + togglePasswordVisibility() { + //@ts-ignore + this.setState((prevState) => ({ + //@ts-ignore + passwordVisible: !prevState.passwordVisible, + })); + } + + @boundMethod + handleInputChange(e: React.ChangeEvent) { + // Call the parent handleInputChange method + super.handleInputChange(e); + } + + render() { + const { decoration, className, placeholder, maxLength, label } = this.props; + const { focused, internalValue, error, passwordVisible } = this.state; + const inputValue = this.props.value ?? internalValue; + + // The input should always receive the real value + const inputProps = { + className: cn("wave-textfield-inner-input", { "offset-left": decoration?.startDecoration }), + ref: this.inputRef, + id: label, + value: inputValue, // Always use the real value here + onChange: this.handleInputChange, + onFocus: this.handleFocus, + onBlur: this.handleBlur, + placeholder: placeholder, + maxLength: maxLength, + }; + + return ( +
+ {decoration?.startDecoration && <>{decoration.startDecoration}} +
+ + + + + + + +
+ + + + + + +
+
+ {decoration?.endDecoration && <>{decoration.endDecoration}} +
+ ); + } +} + +export { PasswordField }; diff --git a/src/app/common/elements/remotestatuslight.less b/src/app/common/elements/remotestatuslight.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/remotestatuslight.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/remotestatuslight.tsx b/src/app/common/elements/remotestatuslight.tsx new file mode 100644 index 000000000..faf51c6bb --- /dev/null +++ b/src/app/common/elements/remotestatuslight.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import { RemoteType } from "../../../types/types"; + +import { ReactComponent as CircleIcon } from "../assets/icons/circle.svg"; +import { ReactComponent as KeyIcon } from "../assets/icons/key.svg"; +import { ReactComponent as RotateIcon } from "../assets/icons/rotate_left.svg"; + +import "./remotestatuslight.less"; + +@mobxReact.observer +class RemoteStatusLight extends React.Component<{ remote: RemoteType }, {}> { + render() { + let remote = this.props.remote; + let status = "error"; + let wfp = false; + if (remote != null) { + status = remote.status; + wfp = remote.waitingforpassword; + } + if (status == "connecting") { + if (wfp) return ; + else return ; + } + return ; + } +} + +export { RemoteStatusLight }; diff --git a/src/app/common/elements/resizablesidebar.less b/src/app/common/elements/resizablesidebar.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/resizablesidebar.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/resizablesidebar.tsx b/src/app/common/elements/resizablesidebar.tsx new file mode 100644 index 000000000..b21701304 --- /dev/null +++ b/src/app/common/elements/resizablesidebar.tsx @@ -0,0 +1,176 @@ +// 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 { GlobalModel, GlobalCommandRunner } from "../../../model/model"; +import { MagicLayout } from "../../magiclayout"; + +import "./common.less"; + +type OV = mobx.IObservableValue; + +interface ResizableSidebarProps { + parentRef: React.RefObject; + position: "left" | "right"; + enableSnap?: boolean; + className?: string; + children?: (toggleCollapsed: () => void) => React.ReactNode; + toggleCollapse?: () => void; +} + +@mobxReact.observer +class ResizableSidebar extends React.Component { + resizeStartWidth: number = 0; + startX: number = 0; + prevDelta: number = 0; + prevDragDirection: string = null; + disposeReaction: any; + + @boundMethod + startResizing(event: React.MouseEvent) { + event.preventDefault(); + + const { parentRef, position } = this.props; + const parentRect = parentRef.current?.getBoundingClientRect(); + + if (!parentRect) return; + + if (position === "right") { + this.startX = parentRect.right - event.clientX; + } else { + this.startX = event.clientX - parentRect.left; + } + + const mainSidebarModel = GlobalModel.mainSidebarModel; + const collapsed = mainSidebarModel.getCollapsed(); + + this.resizeStartWidth = mainSidebarModel.getWidth(); + document.addEventListener("mousemove", this.onMouseMove); + document.addEventListener("mouseup", this.stopResizing); + + document.body.style.cursor = "col-resize"; + mobx.action(() => { + mainSidebarModel.setTempWidthAndTempCollapsed(this.resizeStartWidth, collapsed); + mainSidebarModel.isDragging.set(true); + })(); + } + + @boundMethod + onMouseMove(event: MouseEvent) { + event.preventDefault(); + + const { parentRef, enableSnap, position } = this.props; + const parentRect = parentRef.current?.getBoundingClientRect(); + const mainSidebarModel = GlobalModel.mainSidebarModel; + + if (!mainSidebarModel.isDragging.get() || !parentRect) return; + + let delta: number, newWidth: number; + + if (position === "right") { + delta = parentRect.right - event.clientX - this.startX; + } else { + delta = event.clientX - parentRect.left - this.startX; + } + + newWidth = this.resizeStartWidth + delta; + + if (enableSnap) { + const minWidth = MagicLayout.MainSidebarMinWidth; + const snapPoint = minWidth + MagicLayout.MainSidebarSnapThreshold; + const dragResistance = MagicLayout.MainSidebarDragResistance; + let dragDirection: string; + + if (delta - this.prevDelta > 0) { + dragDirection = "+"; + } else if (delta - this.prevDelta == 0) { + if (this.prevDragDirection == "+") { + dragDirection = "+"; + } else { + dragDirection = "-"; + } + } else { + dragDirection = "-"; + } + + this.prevDelta = delta; + this.prevDragDirection = dragDirection; + + if (newWidth - dragResistance > minWidth && newWidth < snapPoint && dragDirection == "+") { + newWidth = snapPoint; + mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false); + } else if (newWidth + dragResistance < snapPoint && dragDirection == "-") { + newWidth = minWidth; + mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true); + } else if (newWidth > snapPoint) { + mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false); + } + } else { + if (newWidth <= MagicLayout.MainSidebarMinWidth) { + mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true); + } else { + mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false); + } + } + } + + @boundMethod + stopResizing() { + let mainSidebarModel = GlobalModel.mainSidebarModel; + + GlobalCommandRunner.clientSetSidebar( + mainSidebarModel.tempWidth.get(), + mainSidebarModel.tempCollapsed.get() + ).finally(() => { + mobx.action(() => { + mainSidebarModel.isDragging.set(false); + })(); + }); + + document.removeEventListener("mousemove", this.onMouseMove); + document.removeEventListener("mouseup", this.stopResizing); + document.body.style.cursor = ""; + } + + @boundMethod + toggleCollapsed() { + const mainSidebarModel = GlobalModel.mainSidebarModel; + + const tempCollapsed = mainSidebarModel.getCollapsed(); + const width = mainSidebarModel.getWidth(true); + mainSidebarModel.setTempWidthAndTempCollapsed(width, !tempCollapsed); + GlobalCommandRunner.clientSetSidebar(width, !tempCollapsed); + } + + render() { + const { className, children } = this.props; + const mainSidebarModel = GlobalModel.mainSidebarModel; + const width = mainSidebarModel.getWidth(); + const isCollapsed = mainSidebarModel.getCollapsed(); + + return ( +
+
{children(this.toggleCollapsed)}
+
+
+ ); + } +} + +export { ResizableSidebar }; diff --git a/src/app/common/elements/settingserror.less b/src/app/common/elements/settingserror.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/settingserror.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/settingserror.tsx b/src/app/common/elements/settingserror.tsx new file mode 100644 index 000000000..90876b68b --- /dev/null +++ b/src/app/common/elements/settingserror.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 * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; + +import "./settingserror.less"; + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class SettingsError extends React.Component<{ errorMessage: OV }, {}> { + @boundMethod + dismissError(): void { + mobx.action(() => { + this.props.errorMessage.set(null); + })(); + } + + render() { + if (this.props.errorMessage.get() == null) { + return null; + } + return ( +
+
Error: {this.props.errorMessage.get()}
+
+
+ +
+
+ ); + } +} + +export { SettingsError }; diff --git a/src/app/common/elements/showwaveshellinstallprompt.tsx b/src/app/common/elements/showwaveshellinstallprompt.tsx new file mode 100644 index 000000000..cf3a59964 --- /dev/null +++ b/src/app/common/elements/showwaveshellinstallprompt.tsx @@ -0,0 +1,30 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { GlobalModel } from "../../../model/model"; +import * as appconst from "../../appconst"; + +import "./common.less"; + +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). + `; + message = message.trim(); + let prtn = GlobalModel.showAlert({ + message: message, + confirm: true, + markdown: true, + confirmflag: appconst.ConfirmKey_HideShellPrompt, + }); + prtn.then((confirm) => { + if (!confirm) { + return; + } + if (callbackFn) { + callbackFn(); + } + }); +} + +export { ShowWaveShellInstallPrompt }; diff --git a/src/app/common/elements/status.less b/src/app/common/elements/status.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/status.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/status.tsx b/src/app/common/elements/status.tsx new file mode 100644 index 000000000..58005423d --- /dev/null +++ b/src/app/common/elements/status.tsx @@ -0,0 +1,34 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import { boundMethod } from "autobind-decorator"; + +import "./status.less"; + +interface StatusProps { + status: "green" | "red" | "gray" | "yellow"; + text: string; +} + +class Status extends React.Component { + @boundMethod + renderDot() { + const { status } = this.props; + + return
; + } + + render() { + const { text } = this.props; + + return ( +
+ {this.renderDot()} + {text} +
+ ); + } +} + +export { Status }; diff --git a/src/app/common/elements/textfield.less b/src/app/common/elements/textfield.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/textfield.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/textfield.tsx b/src/app/common/elements/textfield.tsx new file mode 100644 index 000000000..a129eb22e --- /dev/null +++ b/src/app/common/elements/textfield.tsx @@ -0,0 +1,173 @@ +// 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 "./textfield.less"; + +interface TextFieldDecorationProps { + startDecoration?: React.ReactNode; + endDecoration?: React.ReactNode; +} +interface TextFieldProps { + label?: string; + value?: string; + className?: string; + onChange?: (value: string) => void; + placeholder?: string; + defaultValue?: string; + decoration?: TextFieldDecorationProps; + required?: boolean; + maxLength?: number; + autoFocus?: boolean; + disabled?: boolean; +} + +interface TextFieldState { + focused: boolean; + internalValue: string; + error: boolean; + showHelpText: boolean; + hasContent: boolean; +} + +class TextField extends React.Component { + inputRef: React.RefObject; + state: TextFieldState; + + constructor(props: TextFieldProps) { + super(props); + const hasInitialContent = Boolean(props.value || props.defaultValue); + this.state = { + focused: false, + hasContent: hasInitialContent, + internalValue: props.defaultValue || "", + error: false, + showHelpText: false, + }; + this.inputRef = React.createRef(); + } + + componentDidUpdate(prevProps: TextFieldProps) { + // Only update the focus state if using as controlled + if (this.props.value !== undefined && this.props.value !== prevProps.value) { + this.setState({ focused: Boolean(this.props.value) }); + } + } + + // Method to handle focus at the component level + @boundMethod + handleComponentFocus() { + if (this.inputRef.current && !this.inputRef.current.contains(document.activeElement)) { + this.inputRef.current.focus(); + } + } + + // Method to handle blur at the component level + @boundMethod + handleComponentBlur() { + if (this.inputRef.current?.contains(document.activeElement)) { + this.inputRef.current.blur(); + } + } + + @boundMethod + handleFocus() { + this.setState({ focused: true }); + } + + @boundMethod + handleBlur() { + const { required } = this.props; + if (this.inputRef.current) { + const value = this.inputRef.current.value; + if (required && !value) { + this.setState({ error: true, focused: false }); + } else { + this.setState({ error: false, focused: false }); + } + } + } + + @boundMethod + handleHelpTextClick() { + this.setState((prevState) => ({ showHelpText: !prevState.showHelpText })); + } + + @boundMethod + handleInputChange(e: React.ChangeEvent) { + const { required, onChange } = this.props; + const inputValue = e.target.value; + + // Check if value is empty and the field is required + if (required && !inputValue) { + this.setState({ error: true, hasContent: false }); + } else { + this.setState({ error: false, hasContent: Boolean(inputValue) }); + } + + // Update the internal state for uncontrolled version + if (this.props.value === undefined) { + this.setState({ internalValue: inputValue }); + } + + onChange && onChange(inputValue); + } + + render() { + const { label, value, placeholder, decoration, className, maxLength, autoFocus, disabled } = this.props; + const { focused, internalValue, error } = this.state; + + // Decide if the input should behave as controlled or uncontrolled + const inputValue = value ?? internalValue; + + return ( +
+ {decoration?.startDecoration && <>{decoration.startDecoration}} +
+ + + + +
+ {decoration?.endDecoration && <>{decoration.endDecoration}} +
+ ); + } +} + +export { TextField }; +export type { TextFieldProps, TextFieldDecorationProps, TextFieldState }; diff --git a/src/app/common/elements/toggle.less b/src/app/common/elements/toggle.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/toggle.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/toggle.tsx b/src/app/common/elements/toggle.tsx new file mode 100644 index 000000000..b155e9464 --- /dev/null +++ b/src/app/common/elements/toggle.tsx @@ -0,0 +1,28 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import { boundMethod } from "autobind-decorator"; + +import "./toggle.less"; + +class Toggle extends React.Component<{ checked: boolean; onChange: (value: boolean) => void }, {}> { + @boundMethod + handleChange(e: any): void { + let { onChange } = this.props; + if (onChange != null) { + onChange(e.target.checked); + } + } + + render() { + return ( + + ); + } +} + +export { Toggle }; diff --git a/src/app/common/elements/tooltip.less b/src/app/common/elements/tooltip.less new file mode 100644 index 000000000..628694b94 --- /dev/null +++ b/src/app/common/elements/tooltip.less @@ -0,0 +1,1153 @@ +@import "../../app/common/themes/themes.less"; + +.info-message { + position: relative; + font-weight: normal; + + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .icon { + display: inline; + width: 1em; + height: 1em; + fill: @base-color; + padding-top: 0.2em; + } + + .info-icon { + margin-right: 5px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + +.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; + } +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 2px; + bottom: 2px; + background-color: @term-white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.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; + } + } +} + +.button.is-wave-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.cmd-hints { + display: flex; + flex-direction: row; + + .hint-item { + padding: 0px 5px 0px 5px; + border-radius: 0 0 3px 3px; + cursor: pointer; + } + + .hint-item:not(:last-child) { + margin-right: 8px; + } + + .hint-item.color-green { + color: @term-black; + background-color: @tab-green; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-green { + color: @term-black; + background-color: @tab-green; + cursor: default; + } + + .hint-item.color-white { + color: @term-black; + background-color: @term-white; + + &:hover { + background-color: @term-bright-white; + } + } + + .hint-item.color-nohover-white { + color: @term-black; + background-color: @term-white; + cursor: default; + } + + .hint-item.color-blue { + color: @term-black; + background-color: @tab-blue; + + &:hover { + color: @term-white; + } + } + + .hint-item.color-nohover-blue { + color: @term-black; + background-color: @tab-blue; + cursor: default; + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + font-family: @markdown-font; + font-size: 14px; + + code { + background-color: @markdown-highlight; + color: @term-white; + font-family: @terminal-font; + border-radius: 4px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + font-family: @terminal-font; + } + + .title { + color: @term-white; + margin-top: 16px; + margin-bottom: 8px; + } + + strong { + color: @term-white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: @term-white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: @markdown-highlight; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: @markdown-highlight; + margin: 4px 10px 4px 10px; + padding: 6px 6px 6px 10px; + border-radius: 4px; + } + + pre.selected { + outline: 2px solid @term-green; + } + + .title.is-1 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + } + .title.is-4 { + } + .title.is-5 { + } + .title.is-6 { + } +} + +.markdown > *:first-child { + margin-top: 0 !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; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .pre { + white-space: pre; + } +} + +.text-button { + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &:hover, + &:focus { + color: @term-white; + background-color: #333; + outline: 2px solid #333; + } + + &.connect-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.disconnect-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +.remote-status { + width: 1em; + height: 1em; + display: inline; + fill: #c4a000; + + &.status-init, + &.status-disconnected { + fill: #c4a000; + } + + &.status-connecting { + fill: #c4a000; + } + + &.status-connected { + fill: #4e9a06; + } + + &.status-error { + fill: #cc0000; + } +} + +.wave-dropdown { + position: relative; + height: 44px; + min-width: 150px; + width: 100%; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + border-radius: 6px; + background: var(--element-hover-2, 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: var(--element-active, 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: var(--olive-dark-1, #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; + animation: waveDropdownMenuFadeOut 0.3s ease-out; +} + +.wave-textfield.wave-password { + .wave-textfield-inner-eye { + position: absolute; + right: 16px; + top: 52%; + transform: translateY(-50%); + transition: transform 0.3s; + + i { + font-size: 14px; + } + } + + .wave-input-decoration { + position: absolute; + top: 0; + height: 100%; + } + + .wave-input-decoration.end-position { + margin-right: 47px; + right: 0; + } + + .wave-input-decoration.start-position { + left: 0; + } +} + +.wave-textfield { + display: flex; + align-items: center; + border-radius: 6px; + position: relative; + height: 44px; + min-width: 412px; + gap: 6px; + border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); + background: var(--element-hover-2, 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; + + &:hover { + cursor: text; + } + + &.focused { + border-color: @term-green; + } + + &.disabled { + opacity: 0.75; + } + + &.error { + border-color: @term-red; + } + + &-inner { + display: flex; + align-items: flex-end; + height: 100%; + position: relative; + flex-grow: 1; + + &-label { + position: absolute; + left: 16px; + top: 16px; + font-size: 12.5px; + transition: all 0.3s; + color: @text-secondary; + line-height: 10px; + + &.float { + font-size: 10px; + top: 5px; + } + + &.offset-left { + left: 0; + } + } + + &-input { + width: 100%; + height: 30px; + border: none; + padding: 5px 0 5px 16px; + font-size: 16px; + outline: none; + background-color: transparent; + color: @term-bright-white; + line-height: 20px; + + &.offset-left { + padding: 5px 16px 5px 0; + } + } + } + + &.no-label { + height: 34px; + + input { + height: 32px; + } + } +} + +.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; +} + +.wave-tooltip { + display: flex; + position: absolute; + z-index: 1000; + flex-direction: row; + align-items: flex-start; + gap: 10px; + padding: 10px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + overflow: hidden; + width: 300px; + + i { + display: inline; + font-size: 13px; + fill: @base-color; + padding-top: 0.2em; + } +} + +.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; + } + } +} + +.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; + } +} + +.wave-status-container { + display: flex; + align-items: center; + + .dot { + height: 6px; + width: 6px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + } + + .dot.green { + background-color: @status-connected; + } + + .dot.red { + background-color: @status-error; + } + + .dot.gray { + background-color: @status-disconnected; + } + + .dot.yellow { + background-color: @status-connecting; + } +} + +.wave-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 500; + + .wave-modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(21, 23, 21, 0.7); + z-index: 1; + } +} + +.wave-modal { + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border-radius: 10px; + background: #151715; + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 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-modal-content { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + + .wave-modal-header { + width: 100%; + display: flex; + align-items: center; + padding: 12px 14px 12px 20px; + justify-content: space-between; + line-height: 20px; + border-bottom: 1px solid rgba(250, 250, 250, 0.1); + + .wave-modal-title { + color: #eceeec; + font-size: 15px; + } + + button { + i { + font-size: 18px; + } + } + } + + .wave-modal-body { + width: 100%; + padding: 0px 20px; + } + + .wave-modal-footer { + display: flex; + justify-content: flex-end; + width: 100%; + padding: 0 20px 20px; + + button:last-child { + margin-left: 8px; + } + } + } +} diff --git a/src/app/common/elements/tooltip.tsx b/src/app/common/elements/tooltip.tsx new file mode 100644 index 000000000..e746392a8 --- /dev/null +++ b/src/app/common/elements/tooltip.tsx @@ -0,0 +1,84 @@ +// 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 ReactDOM from "react-dom"; + +import "./tooltip.less"; + +interface TooltipProps { + message: React.ReactNode; + icon?: React.ReactNode; // Optional icon property + children: React.ReactNode; + className?: string; +} + +interface TooltipState { + isVisible: boolean; +} + +@mobxReact.observer +class Tooltip extends React.Component { + iconRef: React.RefObject; + + constructor(props: TooltipProps) { + super(props); + this.state = { + isVisible: false, + }; + this.iconRef = React.createRef(); + } + + @boundMethod + showBubble() { + this.setState({ isVisible: true }); + } + + @boundMethod + hideBubble() { + this.setState({ isVisible: false }); + } + + @boundMethod + calculatePosition() { + // Get the position of the icon element + const iconElement = this.iconRef.current; + if (iconElement) { + const rect = iconElement.getBoundingClientRect(); + return { + top: `${rect.bottom + window.scrollY - 29}px`, + left: `${rect.left + window.scrollX + rect.width / 2 - 17.5}px`, + }; + } + return {}; + } + + @boundMethod + renderBubble() { + if (!this.state.isVisible) return null; + + const style = this.calculatePosition(); + + return ReactDOM.createPortal( +
+ {this.props.icon &&
{this.props.icon}
} +
{this.props.message}
+
, + document.getElementById("app")! + ); + } + + render() { + return ( +
+ {this.props.children} + {this.renderBubble()} +
+ ); + } +} + +export { Tooltip };