import log from 'electron-log'; import * as React from 'react'; import { Button, Component, Styles, Text, Types, UserInterface, View } from 'reactxp'; import { colors } from '../../config.json'; import styles from './AppButtonStyles'; import ImageView from './ImageView'; const ButtonContext = React.createContext({ textAdjustment: 0, textRef: React.createRef(), }); interface ILabelProps { children?: React.ReactText; } interface IPrivateLabelProps { textAdjustment: number; children?: React.ReactText; } class PrivateLabel extends Component { public render() { const { textAdjustment, children } = this.props; const textAdjustmentStyle = Styles.createViewStyle( { paddingRight: textAdjustment > 0 ? textAdjustment : 0, paddingLeft: textAdjustment < 0 ? Math.abs(textAdjustment) : 0, }, false, ); return ( {children} ); } } export class Label extends Component { public render() { return ( {(context) => ( {this.props.children} )} ); } } interface IIconProps { source: string; width?: number; height?: number; } export class Icon extends Component { public render() { return ( ); } } interface IProps { children?: React.ReactNode; style?: Types.ButtonStyleRuleSet; disabled?: boolean; onPress?: () => void; } interface IState { hovered: boolean; textAdjustment: number; } class BaseButton extends Component { public state: IState = { hovered: false, textAdjustment: 0, }; private containerRef = React.createRef(); private textViewRef = React.createRef(); public componentDidMount() { this.forceUpdateTextAdjustment(); } public render() { const { children, style, ...otherProps } = this.props; return ( ); } protected backgroundStyle = (): Types.ButtonStyleRuleSet => { throw new Error('Implement backgroundStyle in subclasses.'); }; protected onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null); protected onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null); private async forceUpdateTextAdjustment() { const containerView = this.containerRef.current; if (containerView) { const containerLayout = await UserInterface.measureLayoutRelativeToAncestor( containerView, this, ); this.updateTextAdjustment(containerLayout); } } private async updateTextAdjustment(containerLayout: Types.LayoutInfo) { const labelView = this.textViewRef.current; if (labelView) { // calculate the title layout frame const labelLayout = await UserInterface.measureLayoutRelativeToAncestor(labelView, this); // calculate the remaining space at the right hand side const trailingSpace = containerLayout.width - (labelLayout.x + labelLayout.width); // calculate text adjustment const textAdjustment = labelLayout.x - trailingSpace; // re-render the view with the new text adjustment if it changed if (this.state.textAdjustment !== textAdjustment) { this.setState({ textAdjustment }); } } } private onLayout = async (containerLayout: Types.ViewOnLayoutEvent) => { this.updateTextAdjustment(containerLayout); }; } interface IBlockingState { isBlocked: boolean; } interface IBlockingProps { children?: React.ReactNode; onPress: () => Promise; disabled?: boolean; } export class BlockingButton extends Component { public state = { isBlocked: false, }; public render() { return React.Children.map(this.props.children, (child) => { if (React.isValidElement(child)) { return React.cloneElement(child as React.ReactElement, { ...child.props, disabled: this.state.isBlocked || this.props.disabled, onPress: this.onPress, }); } else { return child; } }); } private onPress = () => { this.setState({ isBlocked: true }, async () => { try { await this.props.onPress(); } catch (error) { log.error(`onPress() failed - ${error}`); } this.setState({ isBlocked: false }); }); }; } export class RedButton extends BaseButton { protected backgroundStyle = () => (this.state.hovered ? styles.redHover : styles.red); } export class GreenButton extends BaseButton { protected backgroundStyle = () => (this.state.hovered ? styles.greenHover : styles.green); } export class BlueButton extends BaseButton { protected backgroundStyle = () => (this.state.hovered ? styles.blueHover : styles.blue); } export class TransparentButton extends BaseButton { protected backgroundStyle = () => this.state.hovered ? styles.transparentHover : styles.transparent; } export class RedTransparentButton extends BaseButton { protected backgroundStyle = () => this.state.hovered ? styles.redTransparentHover : styles.redTransparent; }