// @flow
import React, { PureComponent } from 'react'
import isEqual from 'lodash.isequal'
import ClickOutside from 'react-click-outside'
import Button from './Button'
const cmz = require('cmz')
const cx = {
editor: cmz(`
width: 100%
`),
link: cmz(`
& {
text-decoration: none
color: inherit
}
&:hover {
text-decoration: underline
}
`),
button: cmz(`
margin-top: 10px
margin-right: 10px
`),
warningMessage: cmz(`
padding: 10px 0
`)
}
const KEY_CODES = {
ENTER: 13,
ESCAPE: 27
}
const isKeyOfType = (keyCode: number, type: number) => keyCode === type
export type EditorProps = {
value: any,
onValueChange(data: any): any
}
export type PresenterProps = {
value: any,
isEditable: boolean,
isHover: boolean,
activateEditingMode(): any
}
type Props = {
/** Component's value */
value: any,
/** To control whether the component can be editable or not */
isEditable: boolean,
/** To control whether the component begins in presenter mode or edit mode */
initialMode: 'present' | 'edit',
/** To control whether the component can be saved or not */
isValid: boolean,
/** To control whether the component will save changes on Enter or not, the Shift+Enter combination works always */
shouldSaveOnEnter: boolean,
/** Editing mode render function */
editor(props: EditorProps): any,
/** Presentation mode render function */
presenter(props: PresenterProps): any,
/** On save changes callback */
onSave(data: any): any,
/** On cancel changes callback */
onCancel?: () => void,
}
type State = {
editValue: any,
isInEditMode: boolean,
isHover: boolean
}
/**
* A generic component that provides inline editing functionality to any component.
*/
class InlineEditor extends PureComponent<Props, State> {
static defaultProps = {
isEditable: true,
initialMode: 'present',
isValid: true,
shouldSaveOnEnter: true
}
state = {
isInEditMode: this.props.initialMode === 'edit',
isHover: false,
editValue: this.props.value
}
componentWillReceiveProps (nextProps: Props) {
const { value } = nextProps
if (this.props.value !== value && this.state.editValue !== value) {
this.setState({ editValue: value })
}
}
renderControls = () => {
const { editValue } = this.state
const { value, isValid } = this.props
return (
<div>
<Button
className={cx.button}
type='button'
size='small'
rounded
onClick={this.handleSaveClick}
disabled={isEqual(editValue, value) || !isValid}
>
Save
</Button>
<Button
className={cx.button}
type='button'
size='small'
rounded
color='silver'
onClick={this.handleCancelClick}
>
Cancel
</Button>
</div>
)
}
setEditing = (isInEditMode: boolean) => {
let update = { isInEditMode }
if (!isInEditMode) {
update = {
...update,
isHover: false
}
}
this.setState(update)
}
handleContainerClick = () => {
if (this.state.isInEditMode) {
this.abortChanges()
}
}
handleComponentClick = (event: Object) => {
if (this.state.isInEditMode) {
event.stopPropagation()
}
}
handleKeyDown = (event: Object) => {
const { keyCode } = event
const { shouldSaveOnEnter } = this.props
const { ENTER, ESCAPE } = KEY_CODES
const saveChanges = shouldSaveOnEnter
? isKeyOfType(keyCode, ENTER)
: event.shiftKey && isKeyOfType(keyCode, ENTER)
if (saveChanges) {
this.saveChanges()
} else if (isKeyOfType(keyCode, ESCAPE)) {
this.abortChanges()
}
}
handleSaveClick = (event: Object) => {
event.stopPropagation()
this.saveChanges()
}
saveChanges = () => {
const { editValue } = this.state
const { onSave, isValid } = this.props
if (isValid) {
this.setEditing(false)
if (onSave) {
onSave(editValue)
}
}
}
abortChanges = () => {
const { value, onCancel } = this.props
this.setState({
editValue: value,
isInEditMode: false,
isHover: false
})
if (onCancel) {
onCancel()
}
}
handleCancelClick = (event: Object) => {
event.stopPropagation()
this.abortChanges()
}
handleClickOutside = () => {
this.abortChanges()
}
handleValueChange = (value: any) => {
this.setState({ editValue: value })
}
handleActivateEditingMode = () => {
if (this.props.isEditable) {
this.setEditing(true)
}
}
handleMouseEnter = () => {
this.setState({ isHover: true })
}
handleMouseLeave = () => {
this.setState({ isHover: false })
}
renderContent = () => {
const { editValue, isHover, isInEditMode } = this.state
const { editor, presenter, isEditable } = this.props
const mainComponentRenderer = isInEditMode ? editor : presenter
const props = {
value: editValue,
onValueChange: this.handleValueChange,
activateEditingMode: this.handleActivateEditingMode,
isEditable,
isHover
}
return (
<div
onClick={this.handleContainerClick}
onKeyDown={this.handleKeyDown}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
<div onClick={this.handleComponentClick}>
{mainComponentRenderer && mainComponentRenderer(props)}
</div>
{isInEditMode && this.renderControls()}
</div>
)
}
render () {
return this.state.isInEditMode
? (
<div className={cx.editor}>
<ClickOutside onClickOutside={this.handleClickOutside}>
{this.renderContent()}
</ClickOutside>
</div>
) : this.renderContent()
}
}
export default InlineEditor
const UnionType = require("./UnionType");
const LiteralType = require("./LiteralType");
const Type = require("./Type.js");
const Utils = require("../core/Utils");
class KeyofType extends UnionType{
constructor(target, referenceType){
super([],target);
this.isKeyofType = true;
this.referenceType = referenceType;
this.hasGenericType = !!referenceType.isGenericType;
this.elements = this.getTypeKeys(referenceType)
}
clone(inference){
if( !inference || !this.hasGenericType ){
return this;
}
const type = Utils.inferTypeValue( this.referenceType.type(), inference );
if( type !== this.referenceType ){
return new KeyofType(this.target, type );
}
return this;
}
getTypeKeys( type ){
type = type || this.referenceType;
if( type.isGenericType ){
return [];
}
const properties = new Set();
if( type ){
const push=(name,type)=>{
properties.add( new LiteralType( this.target.getGlobalTypeById(type), this.target, name) );
}
switch(true){
case type.isLiteralObjectType :
case type.isEnumType :
type.attributes.forEach( (value,key)=>{
push(key, 'string');
});
break;
case type.isLiteralArrayType :
case type.isTupleUnion :
type.elements.forEach( (value,key)=>{
push(key, 'number');
});
break;
case type.isIntersectionType :
case type.isModule :
case type.isInstanceofType :
type.getTypeKeys().forEach( key=>{
push(key, 'string');
});
break
}
}
return Array.from( properties.values() );
}
is(type, context){
if( !type || !(type instanceof Type) )return false;
if( !this.isNeedCheckType(type) )return true;
if( type.isAliasType ){
const inherit = type.inherit;
if( inherit && inherit.isUnionType ){
type = inherit;
}
}
const infer = type=>{
if( context && context.inference ){
return context.inference(type);
}
return type;
};
const elements = this.getTypeKeys( infer( this.referenceType ) );
if( !elements.length )return false;
if( type.isUnionType ){
return type.elements.every( item=>{
const base = item.type();
return elements.some( child=>{
const childType = child.type();
if( base.isLiteralType ){
return childType.isLiteralType ? base.value === childType.value : false;
}
return base.is(child.type(), context);
});
});
}
return elements.some( base=>base.type().is(type, context) );
}
toString(context={}){
const refType = this.referenceType.type();
if( refType.hasGenericType ){
if( refType.isGenericType && refType.hasConstraint && !refType.inherit.hasGenericType ){
return `keyof ${refType.inherit.toString(context)}`;
}
return `keyof ${refType.toString(context)}`;
}
let elements = this.getTypeKeys( refType );
if( !elements.length ){
return 'never';
}
return elements.map( item=>item.type().toString(context) ).join(' | ');
}
}
module.exports = KeyofType;
const Utils = require("../core/Utils");
const Namespace = require("../core/Namespace");
const Type = require("./Type");
class TupleType extends Type{
constructor(inherit, elements , target, rest = false , isTupleUnion=false){
super("$TupleType",inherit);
this.elements = [].concat(elements);
const len = this.elements.length;
this.rest = rest;
this.requireCount = rest && len > 1 ? len-1 : len;
this.isTupleType = true;
this.prefix = !!(target && target.prefix);
this.isTupleUnion = isTupleUnion ? true : !!(target && target.isTypeTupleUnionDefinition);
this.target = target;
}
get hasGenericType(){
return this.elements.some( type=>{
type = type.type();
return type && type.hasGenericType;
});
}
attribute(index){
if( !(this.prefix || this.isTupleUnion) ){
return this.elements[ index ] || null;
}
return null;
}
dynamicAttribute( type ){
const arrClass = Namespace.globals.get('Array');
return arrClass && arrClass.dynamicProperties.get( Utils.getOriginType( type ) );
}
clone(inference){
if( !inference || !this.hasGenericType ){
return this;
}
const elements = this.elements.map( item=>{
return Utils.inferTypeValue(item.type(), inference);
});
return new TupleType(this.inherit,elements,this.target,this.rest, this.isTupleUnion);
}
checkItems(items, errorItems=[], context={} ){
const infer = type =>Utils.inferTypeValue(type, context.inference);
const errorHandler = context.errorHandler || ( result=>result );
const checkItem = (base, item, flag=true)=>{
const baseType = infer( base.type() );
const type = infer( item.type() );
if( ( base.isThisType || (base.target && base.target.isThisType) ) && !type.isInstanceofType ){
errorItems.push( item );
return flag ? errorHandler(false, baseType, item) : false;
}
if( baseType && !baseType.is( type, context ) ){
errorItems.push( item );
return flag ? errorHandler(false, baseType, item) : false;
}
return true;
}
if(this.prefix || this.rest || this.isTupleUnion){
return items.every( (item)=>{
return errorHandler(this.elements.some( base=>{
return checkItem(base, item, false);
}), this.elements[0].type(), item);
});
}
const len = this.elements.length;
const rest = len > 0 ? this.elements[ len-1 ] : null;
const hasRest = rest && rest.type().rest;
const requireCount = hasRest ? this.requireCount-1 : this.requireCount;
if( (hasRest && items.length < requireCount) ){
return false;
}else if( !hasRest && items.length !== requireCount ){
return false;
}
return items.every( (item,index)=>{
let base = this.elements[index];
if( base && !(hasRest && base === rest) ){
return checkItem(base,item);
}else{
if( hasRest && rest ){
return checkItem(rest,item);
}else{
return errorHandler(this.elements.some( (base)=>{
return checkItem(base,item,false);
}), this.elements[0].type(), item );
}
}
});
}
is( type, context ){
if( !type || !(type instanceof Type) )return false;
if( !this.isNeedCheckType(type) )return true;
if( type.isAliasType ){
const inherit = type.inherit;
if( inherit && inherit.isTupleType ){
type = inherit;
}
}
if( this.isTupleUnion && !this.inherit.is(type.inherit, context) ){
return false;
}
let items = [];
if( type.isTupleType || type.isLiteralArrayType){
items =type.elements;
}else if( type.isClassGenericType && type.inherit === this.inherit ){
items = type.types;
}else if( !this.rest ){
return this.inherit.is(type, context);
}
return this.checkItems( items , [], context);
}
toString(context){
const isTupleUnion = this.isTupleUnion || this.elements.some( item=>{
let type = item.type();
if( type.isComputeType && !type.object.isThisType ){
type = type.property.type();
}
return !!type.isUnionType;
});
context = {parent:context,type:this,isTupleUnion};
const elements = this.elements.map( (item)=>{
return item.type().toString(context);
});
const rest = this.rest && this.target && !this.target.isRestElement ? '...' : '';
if( isTupleUnion ){
if( elements.length > 1 || ( (this.elements[0].isUnionType || this.elements[0].isKeyofType) && this.elements[0].elements.length > 1) ){
return `(${elements.join(" | ")})[]`;
}else{
return `${elements[0]}[]`;
}
}
if( elements.length === 1 && (this.prefix||this.rest) ){
return `${rest}${elements[0]}[]`;
}
return `${rest}[${elements.join(',')}]`;
}
}
module.exports = TupleType;
Accelerate Your Automation Test Cycles With LambdaTest
Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.