/** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal */ import * as React from 'react'; import { Color } from '../../../mol-util/color'; import { PurePluginUIComponent } from '../base'; export class ControlGroup extends React.Component<{ header: string, initialExpanded?: boolean }, { isExpanded: boolean }> { state = { isExpanded: !!this.props.initialExpanded } toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded }); render() { return
{this.state.isExpanded &&
{this.props.children}
}
} } export interface TextInputProps { className?: string, style?: React.CSSProperties, value: T, fromValue?(v: T): string, toValue?(s: string): T, // TODO: add error/help messages here? isValid?(s: string): boolean, onChange(value: T): void, onEnter?(): void, onBlur?(): void, delayMs?: number, blurOnEnter?: boolean, blurOnEscape?: boolean, isDisabled?: boolean, placeholder?: string } interface TextInputState { originalValue: string, value: string } function _id(x: any) { return x; } export class TextInput extends PurePluginUIComponent, TextInputState> { private input = React.createRef(); private delayHandle: any = void 0; private pendingValue: T | undefined = void 0; state = { originalValue: '', value: '' } onBlur = () => { this.setState({ value: '' + this.state.originalValue }); if (this.props.onBlur) this.props.onBlur(); } get isPending() { return typeof this.delayHandle !== 'undefined'; } clearTimeout() { if (this.isPending) { clearTimeout(this.delayHandle); this.delayHandle = void 0; } } raiseOnChange = () => { this.props.onChange(this.pendingValue!); this.pendingValue = void 0; } triggerChanged(formatted: string, converted: T) { this.clearTimeout(); if (formatted === this.state.originalValue) return; if (this.props.delayMs) { this.pendingValue = converted; this.delayHandle = setTimeout(this.raiseOnChange, this.props.delayMs); } else { this.props.onChange(converted); } } onChange = (e: React.ChangeEvent) => { const value = e.target.value; if (this.props.isValid && !this.props.isValid(value)) { this.clearTimeout(); this.setState({ value }); return; } const converted = (this.props.toValue || _id)(value); const formatted = (this.props.fromValue || _id)(converted); this.setState({ value: formatted }, () => this.triggerChanged(formatted, converted)); } onKeyUp = (e: React.KeyboardEvent) => { if (e.charCode === 27 || e.keyCode === 27 /* esc */) { if (this.props.blurOnEscape && this.input.current) { this.input.current.blur(); } } } onKeyPress = (e: React.KeyboardEvent) => { if (e.keyCode === 13 || e.charCode === 13 /* enter */) { if (this.isPending) { this.clearTimeout(); this.raiseOnChange(); } if (this.props.blurOnEnter && this.input.current) { this.input.current.blur(); } if (this.props.onEnter) this.props.onEnter(); } e.stopPropagation(); } static getDerivedStateFromProps(props: TextInputProps, state: TextInputState) { const value = props.fromValue ? props.fromValue(props.value) : props.value; if (value === state.originalValue) return null; return { originalValue: value, value }; } render() { return ; } } // TODO: replace this with parametrized TextInput export class NumericInput extends React.PureComponent<{ value: number, onChange: (v: number) => void, onEnter?: () => void, onBlur?: () => void, blurOnEnter?: boolean, isDisabled?: boolean, placeholder?: string }, { value: string }> { state = { value: '0' }; input = React.createRef(); onChange = (e: React.ChangeEvent) => { const value = +e.target.value; this.setState({ value: e.target.value }, () => { if (!Number.isNaN(value) && value !== this.props.value) { this.props.onChange(value); } }); } onKeyPress = (e: React.KeyboardEvent) => { if ((e.keyCode === 13 || e.charCode === 13)) { if (this.props.blurOnEnter && this.input.current) { this.input.current.blur(); } if (this.props.onEnter) this.props.onEnter(); } e.stopPropagation(); } onBlur = () => { this.setState({ value: '' + this.props.value }); if (this.props.onBlur) this.props.onBlur(); } static getDerivedStateFromProps(props: { value: number }, state: { value: string }) { const value = +state.value; if (Number.isNaN(value) || value === props.value) return null; return { value: '' + props.value }; } render() { return } } export function Icon(props: { name: string }) { return ; } export function IconButton(props: { icon: string, isSmall?: boolean, onClick: (e: React.MouseEvent) => void, title?: string, toggleState?: boolean, disabled?: boolean, customClass?: string, 'data-id'?: string }) { let className = `msp-btn-link msp-btn-icon${props.isSmall ? '-small' : ''}${props.customClass ? ' ' + props.customClass : ''}`; if (typeof props.toggleState !== 'undefined') className += ` msp-btn-link-toggle-${props.toggleState ? 'on' : 'off'}` return ; } export class ExpandableGroup extends React.Component<{ label: string, colorStripe?: Color, pivot: JSX.Element, controls: JSX.Element }, { isExpanded: boolean }> { state = { isExpanded: false }; toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded }); render() { const { label, pivot, controls } = this.props; // TODO: fix the inline CSS return <>
{label}
{pivot}
{this.props.colorStripe &&
}
{this.state.isExpanded &&
{controls}
} ; } } export class ButtonSelect extends React.PureComponent<{ label: string, onChange: (value: string) => void, disabled?: boolean }> { onChange = (e: React.ChangeEvent) => { e.preventDefault() this.props.onChange(e.target.value) e.target.value = '_' } render() { return } } export function Options(options: [string, string][]) { return options.map(([value, label]) => ) } // export const ToggleButton = (props: { // onChange: (v: boolean) => void, // value: boolean, // label: string, // title?: string // }) =>
// {props.label} //
// //
//