lianghuang 4 lat temu
rodzic
commit
cc4bb4ccd4

+ 168 - 0
coms/jenga/block/base/ConItem.jsx

@@ -0,0 +1,168 @@
+import React from 'react'
+import { is } from 'immutable'
+import i18n from 'i18next'
+import newEventStores from 'stores/newEvent'
+import uiStore from 'stores/ui'
+import { conFlags, conOperatorOps } from 'const/enum'
+import { shallowEqualImmutable } from 'utils/utils'
+import { getConSpecialParams } from 'src/components/common/formulaEditor/utils'
+
+export default class ConItem extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {
+      conItem: this.getConItem(props.index),
+      conFlags: conFlags,
+      conOperatorOptions: conOperatorOps,
+      widthDelta: uiStore.jengaViewWidthDelta
+    }
+
+    this.onEventChange = this.onEventChange.bind(this)
+    this.onUiChange = this.onUiChange.bind(this)
+
+    this.onToggleEnable = this.onToggleEnable.bind(this)
+    this.onChangeValue = this.onChangeValue.bind(this)
+    this.onChangeFlag = this.onChangeFlag.bind(this)
+    this.onChangeOperator = this.onChangeOperator.bind(this)
+    this.onChangeCon = this.onChangeCon.bind(this)
+    this.onAddCon = this.onAddCon.bind(this)
+
+    this.isSelectedFlag = this.isSelectedFlag.bind(this)
+    this.isSelectedOperator = this.isSelectedOperator.bind(this)
+
+    this.getConItem = this.getConItem.bind(this)
+    this.getConItemFlagName = this.getConItemFlagName.bind(this)
+    this.getConItemOperatorName = this.getConItemOperatorName.bind(this)
+    this.getOptionName = this.getOptionName.bind(this)
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    return (
+      !shallowEqualImmutable(this.props, nextProps) ||
+      !shallowEqualImmutable(this.state, nextState)
+    )
+  }
+
+  componentDidMount() {
+    this.unsubscribe = newEventStores.listen(this.onEventChange)
+    this.unsubscribeUI = uiStore.listen(this.onUiChange)
+  }
+
+  componentWillUnmount() {
+    this.unsubscribe()
+    this.unsubscribeUI()
+  }
+
+  componentDidUpdate(preProps) {
+    let conItem = this.getConItem()
+    if (!is(this.state.conItem, conItem) || preProps.bid !== this.props.bid) {
+      this.setState({
+        conItem: conItem
+      })
+    }
+  }
+
+  onEventChange(status) {
+    let obj = {}
+    if (status && status.types) {
+      if (
+        status.types.includes('updateBlockProp') &&
+        this.props.bid === status.data.affectBlockId &&
+        status.data.prop === 'cons'
+      ) {
+        this.forceUpdate()
+      }
+    }
+    if (Object.keys(obj).length > 0) {
+      this.setState(obj)
+    }
+  }
+
+  onUiChange(status) {
+    if (status && status.types) {
+      if (status.types.includes('changeJengaViewWidthDelta')) {
+        if (status.data.jengaViewWidthDelta !== this.state.widthDelta) {
+          this.setState({
+            widthDelta: status.data.jengaViewWidthDelta
+          })
+        }
+      }
+    }
+  }
+
+  onToggleEnable() {
+    this.onChangeCon('enable', !this.state.conItem.enable)
+  }
+
+  onChangeValue(prop, value) {
+    this.onChangeCon(prop, value, false)
+  }
+
+  onChangeFlag(option) {
+    if (option && option.key) {
+      let value = option.key
+      this.onChangeCon('flag', value)
+    }
+  }
+
+  onChangeOperator(option) {
+    if (option && option.key) {
+      let value = option.key
+      this.onChangeCon('operator', value)
+    }
+  }
+
+  onAddCon() {
+    this.props.onAddBlockCon()
+    this.props.onConsChange()
+  }
+
+  onChangeCon(prop, value, forceUpdate = true) {
+    let con = this.state.conItem
+    con[prop] = value
+    this.props.onConsChange()
+  }
+
+  isSelectedFlag(flag) {
+    return this.state.conItem ? flag === this.state.conItem.flag : false
+  }
+
+  isSelectedOperator(operator) {
+    return this.state.conItem ? operator === this.state.conItem.operator : false
+  }
+
+  getConItem() {
+    return this.props.con || {}
+  }
+
+  getConItemFlagName() {
+    return this.state.conItem && this.state.conItem.flag
+      ? i18n.t('EventView.jenga_cons.' + this.state.conItem.flag)
+      : ''
+  }
+
+  getConItemOperatorName() {
+    return this.state.conItem && this.state.conItem.operator
+      ? i18n.t('EventView.jenga_cons.' + this.state.conItem.operator)
+      : ''
+  }
+
+  getOptionName(option) {
+    return i18n.t('EventView.jenga_cons.' + option)
+  }
+
+  formulaExtraParams() {
+    if (
+      this.state.conItem &&
+      ['match', 'notMatch'].indexOf(this.state.conItem.operator) >= 0
+    ) {
+      return getConSpecialParams()
+    } else {
+      return undefined
+    }
+  }
+
+  render() {
+    return null
+  }
+}

+ 5 - 1
coms/jenga/block/base/action/BlockActionObject.jsx

@@ -436,7 +436,11 @@ export default class BlockActionObject extends React.Component {
 
     return (
       <div className="action-object f--h">
-        <BlockEnable bid={this.props.bid} isEnable={this.props.isEnable} />
+        <BlockEnable
+          bid={this.props.bid}
+          isEnable={this.props.isEnable}
+          eventNodeId={this.props.eventNodeId}
+        />
         <div className="object-wrap">
           <div className="select-wrap f--hlc">
             <Dropdown

+ 294 - 0
coms/jenga/block/base/con/BlockConConItem.jsx

@@ -0,0 +1,294 @@
+import React from 'react'
+import cls from 'classnames'
+import { Menu, Dropdown } from 'antd'
+import i18n from 'i18next'
+import newEventActions from 'actions/newEvent'
+import treeStores from 'stores/tree'
+import { getEventBlockByBid, layerWidth } from 'stores/funcs/newEvent'
+import { conOptions } from 'const/enum'
+import { is } from 'immutable'
+import ConItem from '../ConItem'
+import FormulaEditor from 'src/components/common/formulaEditor/EventFormulaEditor'
+
+const MenuItem = Menu.Item
+
+export default class BlockConConItem extends ConItem {
+  constructor(props) {
+    super(props)
+    this.state.option = this.getConOption(props.bid)
+    this.state.conOptions = conOptions
+
+    this.onChangeConOption = this.onChangeConOption.bind(this)
+    this.onDeleteCon = this.onDeleteCon.bind(this)
+
+    this.isSelectedOption = this.isSelectedOption.bind(this)
+
+    this.getValueWidth = this.getValueWidth.bind(this)
+    this.getConOption = this.getConOption.bind(this)
+    this.getConOptionName = this.getConOptionName.bind(this)
+  }
+
+  componentDidUpdate(preProps) {
+    let obj = {}
+    let conItem = this.getConItem()
+    if (!is(this.state.conItem, conItem) || preProps.bid !== this.props.bid) {
+      obj.conItem = conItem
+      if (preProps.bid !== this.props.bid) {
+        let option = this.getConOption()
+        if (this.state.option !== option) {
+          obj.option = option
+        }
+      }
+    }
+    if (Object.keys(obj).length > 0) {
+      this.setState(obj)
+    }
+  }
+
+  onEventChange(status) {
+    let obj = {}
+    if (status && status.types) {
+      if (
+        status.types.includes('updateBlockProp') &&
+        this.props.bid === status.data.affectBlockId &&
+        status.data.prop === 'op'
+      ) {
+        let option = this.getConOption()
+        if (this.state.option !== option) {
+          obj.option = option
+        }
+      }
+
+      if (
+        status.types.includes('updateBlockProp') &&
+        this.props.bid === status.data.affectBlockId &&
+        status.data.prop === 'cons'
+      ) {
+        this.forceUpdate()
+      }
+    }
+    if (Object.keys(obj).length > 0) {
+      this.setState(obj)
+    }
+  }
+
+  onChangeConOption(option) {
+    if (option && option.key) {
+      let value = option.key
+      this.setState(
+        {
+          option: value
+        },
+        () => {
+          newEventActions.updateBlockProp({
+            blockId: this.props.bid,
+            prop: 'op',
+            value: value
+          })
+        }
+      )
+    }
+  }
+
+  onDeleteCon() {
+    this.props.onDelBlockCon({ index: this.props.index, resetLast: true })
+    this.props.onConsChange()
+  }
+
+  isSelectedOption(option) {
+    return this.state.option === option
+  }
+
+  getValueWidth() {
+    const groupLayer = this.props.groupLayer > 0 ? this.props.groupLayer : 0
+    const nestingLayer =
+      this.props.nestGroupLayer > 0 ? this.props.nestGroupLayer - 1 : 0
+    const extraGapGroupLayer =
+      this.props.extraGapGroupLayer > 0
+        ? (this.props.extraGapGroupLayer - 1) * 2
+        : 0
+    let originWidth =
+      304 - ((groupLayer + nestingLayer) * 2) / 2 - extraGapGroupLayer / 2
+    let width = layerWidth({
+      originWidth: originWidth + this.state.widthDelta / 2,
+      decreaseFactor: 10.5,
+      layer: this.props.layer
+    })
+    return {
+      width: width + 'px'
+    }
+  }
+
+  getConOption(bid) {
+    let _bid = bid || this.props.bid
+    let nodeId = this.props.eventNodeId || treeStores.curEventNodeId
+    let curBlock = getEventBlockByBid(_bid, nodeId)
+    if (curBlock) {
+      return curBlock.op || ''
+    }
+    return ''
+  }
+
+  getConOptionName() {
+    let option = this.state.option
+    if (option) {
+      return i18n.t('EventView.jenga_cons.' + option)
+    }
+    return ''
+  }
+
+  render() {
+    let conOptionsMenu = () => {
+      return (
+        <Menu
+          className="con-con-flag-select-menu"
+          onClick={this.onChangeConOption}
+        >
+          {this.state.conOptions.map(option => {
+            return (
+              <MenuItem
+                key={option}
+                className={cls({
+                  selected: this.isSelectedOption(option)
+                })}
+              >
+                {this.getOptionName(option)}
+              </MenuItem>
+            )
+          })}
+        </Menu>
+      )
+    }
+
+    let conFlagsMenu = () => {
+      return (
+        <Menu className="con-con-flag-select-menu" onClick={this.onChangeFlag}>
+          {this.state.conFlags.map(option => {
+            return (
+              <MenuItem
+                key={option}
+                className={cls({
+                  selected: this.isSelectedFlag(option)
+                })}
+              >
+                {this.getOptionName(option)}
+              </MenuItem>
+            )
+          })}
+        </Menu>
+      )
+    }
+
+    let conOperatorsMenu = () => {
+      return (
+        <Menu
+          className="con-con-operator-select-menu"
+          onClick={this.onChangeOperator}
+        >
+          {this.state.conOperatorOptions.map(option => {
+            return (
+              <MenuItem
+                key={option}
+                className={cls({
+                  selected: this.isSelectedOperator(option)
+                })}
+              >
+                {option}
+              </MenuItem>
+            )
+          })}
+        </Menu>
+      )
+    }
+
+    return (
+      <div className={cls('con-item f--hlc')}>
+        {this.props.index === 0 ? (
+          <React.Fragment>
+            <div className="item-enable" />
+            <Dropdown
+              trigger={['click']}
+              getPopupContainer={triggerNode => triggerNode.parentNode}
+              overlayClassName={'con-con-flag-dropdown'}
+              overlay={conOptionsMenu()}
+            >
+              <div className="con-con-flag-select f--hlc">
+                <input
+                  value={this.getConOptionName()}
+                  readOnly={true}
+                  className="ant-dropdown-input"
+                />
+                <div className="ant-dropdown-icon" />
+              </div>
+            </Dropdown>
+          </React.Fragment>
+        ) : (
+          <React.Fragment>
+            <div className="item-enable" />
+            <Dropdown
+              trigger={['click']}
+              getPopupContainer={triggerNode => triggerNode.parentNode}
+              overlayClassName={'con-con-flag-dropdown'}
+              overlay={conFlagsMenu()}
+            >
+              <div className="con-con-flag-select f--hlc">
+                <input
+                  value={this.getConItemFlagName()}
+                  readOnly={true}
+                  className="ant-dropdown-input"
+                />
+                <div className="ant-dropdown-icon" />
+              </div>
+            </Dropdown>
+          </React.Fragment>
+        )}
+        <div className="item-value-wrap f--h" style={this.getValueWidth()}>
+          <FormulaEditor
+            className="event-formula-editor"
+            uid={this.props.bid + 'value1'}
+            bid={this.props.bid}
+            value={this.state.conItem.value1}
+            onChangeCode={this.onChangeValue.bind(this, 'value1')}
+          />
+        </div>
+        <Dropdown
+          trigger={['click']}
+          getPopupContainer={triggerNode => triggerNode.parentNode}
+          overlayClassName={'con-con-operator-dropdown'}
+          overlay={conOperatorsMenu()}
+        >
+          <div className="con-con-operator-select f--hlc">
+            <input
+              value={this.state.conItem.operator}
+              readOnly={true}
+              className="ant-dropdown-input"
+            />
+            <div className="ant-dropdown-icon" />
+          </div>
+        </Dropdown>
+        <div className="item-value-wrap f--h" style={this.getValueWidth()}>
+          <FormulaEditor
+            className="event-formula-editor"
+            uid={this.props.bid + 'value2'}
+            bid={this.props.bid}
+            value={this.state.conItem.value2}
+            params={this.formulaExtraParams()}
+            onChangeCode={this.onChangeValue.bind(this, 'value2')}
+          />
+        </div>
+        <button
+          className="btn-clear btn-con-item-del"
+          onClick={this.onDeleteCon}
+        />
+        {this.props.index === 0 ? (
+          <button className="btn-clear btn-add-con" onClick={this.onAddCon}>
+            <div className="btn-icon">
+              <span className="horizon" />
+              <span className="vertical" />
+            </div>
+          </button>
+        ) : null}
+      </div>
+    )
+  }
+}

+ 244 - 0
coms/jenga/block/base/con/BlockConCons.jsx

@@ -0,0 +1,244 @@
+import React from 'react'
+import newEventActions from 'actions/newEvent'
+import treeStores from 'stores/tree'
+import { getEventBlockByBid, emptyCon } from 'stores/funcs/newEvent'
+import newEventStores from 'stores/newEvent'
+import { cpJson, shallowEqualImmutable } from 'utils/utils'
+import { BLOCK_OP_TYPE } from 'const/const'
+import BlockEventConItem from './BlockConConItem'
+
+export default class BlockConCons extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {
+      cons: this.getCons()
+    }
+
+    this.onEventChange = this.onEventChange.bind(this)
+    this.getCons = this.getCons.bind(this)
+    this.changeConsAst = this.changeConsAst.bind(this)
+    this.addBlockCon = this.addBlockCon.bind(this)
+    this.delBlockCon = this.delBlockCon.bind(this)
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    return (
+      !shallowEqualImmutable(this.props, nextProps) ||
+      !shallowEqualImmutable(this.state, nextState)
+    )
+  }
+
+  componentDidMount() {
+    this.unsubscribe = newEventStores.listen(this.onEventChange)
+  }
+
+  componentWillUnmount() {
+    this.unsubscribe()
+  }
+
+  componentDidUpdate(preProps) {
+    if (preProps.bid !== this.props.bid) {
+      this.setState({
+        cons: this.getCons()
+      })
+    }
+  }
+
+  onEventChange(status) {
+    let obj = {}
+    if (status && status.types) {
+      if (
+        status.types.includes('updateBlockProp') &&
+        this.props.bid === status.data.affectBlockId &&
+        status.data.prop === 'cons'
+      ) {
+        // 这里需要强制更新, 因为cons是个对象
+        this.forceUpdate()
+      }
+    }
+    if (Object.keys(obj).length > 0) {
+      this.setState(obj)
+    }
+  }
+
+  genConItem({ obj, flag }) {
+    let result = {}
+    result['flag'] = flag
+    result['operator'] = obj.op
+    result['value1'] = obj.args[0].expVal
+    result['value2'] = obj.args[1].expVal
+    return result
+  }
+
+  getCons() {
+    let result = []
+    let nodeId = this.props.eventNodeId || treeStores.curEventNodeId
+    let block = getEventBlockByBid(this.props.bid, nodeId)
+    if (
+      block &&
+      (block.op === BLOCK_OP_TYPE.IF || block.op === BLOCK_OP_TYPE.ELSE)
+    ) {
+      let conObj = block.args[0]
+      if (conObj && conObj.op) {
+        switch (conObj.op) {
+          case 'or':
+            let orArgs = conObj.args
+            orArgs.forEach(orItem => {
+              if (orItem.op === 'and') {
+                let andArgs = orItem.args
+                andArgs.forEach((andItem, index) => {
+                  let flag = index === 0 ? 'or' : 'and'
+                  let con = this.genConItem({ obj: andItem, flag })
+                  result.push(con)
+                })
+              } else {
+                let con = this.genConItem({ obj: orItem, flag: 'or' })
+                result.push(con)
+              }
+            })
+            break
+          case 'and':
+            let andArgs = conObj.args
+            andArgs.forEach(andItem => {
+              let con = this.genConItem({ obj: andItem, flag: 'and' })
+              result.push(con)
+            })
+            break
+          default:
+            let con = this.genConItem({ obj: conObj, flag: 'and' })
+            result.push(con)
+        }
+      }
+    }
+    return result
+  }
+
+  genConObj(conItem) {
+    let obj = {
+      op: conItem.operator,
+      args: [
+        { op: 'var', expVal: conItem.value1, args: [] },
+        { op: 'var', expVal: conItem.value2, args: [] }
+      ]
+    }
+    return obj
+  }
+
+  changeConsAst() {
+    // 将cons数组转为ast格式
+    let cons = cpJson(this.state.cons)
+    let ors = []
+    let ands = []
+    cons.forEach((con, index) => {
+      if (con.flag !== 'or') {
+        ands.push(con)
+      } else {
+        ors.push(ands)
+        ands = []
+        ands.push(con)
+      }
+      if (index === cons.length - 1) {
+        ors.push(ands)
+      }
+    })
+
+    let consAst = {}
+    if (ors.length > 1) {
+      // 有or
+      consAst.op = 'or'
+      consAst.args = []
+      ors.forEach(orItem => {
+        if (orItem.length > 1) {
+          // 多个条件
+          let andObj = {
+            op: 'and',
+            args: []
+          }
+          orItem.forEach(conItem => {
+            let conObj = this.genConObj(conItem)
+            andObj.args.push(conObj)
+          })
+          consAst.args.push(andObj)
+        } else {
+          // 单个条件
+          let conObj = this.genConObj(orItem[0])
+          consAst.args.push(conObj)
+        }
+      })
+    } else {
+      // 没有or
+      consAst.op = 'and'
+      consAst.args = []
+      let andArray = ors[0]
+      if (andArray.length > 1) {
+        // 多个条件
+        andArray.forEach(conItem => {
+          let conObj = this.genConObj(conItem)
+          consAst.args.push(conObj)
+        })
+      } else {
+        // 单个条件
+        consAst = this.genConObj(andArray[0])
+      }
+    }
+
+    let nodeId = this.props.eventNodeId || treeStores.curEventNodeId
+    newEventActions.updateBlockProp({
+      nodeId,
+      blockId: this.props.bid,
+      prop: 'cons',
+      value: consAst,
+      trigger: true,
+      addRecord: true
+    })
+  }
+
+  addBlockCon() {
+    let cons = this.state.cons
+    let newEmptyCon = emptyCon()
+    cons.push(newEmptyCon)
+  }
+
+  delBlockCon({ index, resetLast }) {
+    let cons = this.state.cons
+    if (resetLast && cons.length === 1) {
+      cons[0] = emptyCon()
+    } else {
+      cons.splice(index, 1)
+    }
+    if (cons.length === 1) {
+      cons[0].flag = 'and'
+    }
+  }
+
+  render() {
+    let cons = (
+      <div className="con-items-wrap">
+        {this.state.cons.map((con, index) => {
+          return (
+            <BlockEventConItem
+              index={index}
+              con={con}
+              key={index}
+              layer={this.props.layer}
+              groupLayer={this.props.groupLayer}
+              nestGroupLayer={this.props.nestGroupLayer}
+              extraGapGroupLayer={this.props.extraGapGroupLayer}
+              bid={this.props.bid}
+              eventNodeId={this.props.eventNodeId}
+              onConsChange={this.changeConsAst}
+              onAddBlockCon={this.addBlockCon}
+              onDelBlockCon={this.delBlockCon}
+            />
+          )
+        })}
+      </div>
+    )
+
+    return (
+      <div className="con-cons flex-1">
+        {!this.state.cons || this.state.cons.length === 0 ? null : cons}
+      </div>
+    )
+  }
+}

+ 164 - 0
coms/jenga/block/base/loop/BlockLoopOption.jsx

@@ -0,0 +1,164 @@
+import React from 'react'
+import { Menu, Dropdown } from 'antd'
+import i18n from 'i18next'
+import cls from 'classnames'
+import newEventActions from 'actions/newEvent'
+import newEventStores from 'stores/newEvent'
+import { getEventBlockByBid } from 'stores/funcs/newEvent'
+import { shallowEqualImmutable } from 'utils/utils'
+import { loopOptions } from 'const/enum'
+import { BLOCK_LOOP_TYPE } from 'const/const'
+import BlockEnable from '../BlockEnable'
+
+const MenuItem = Menu.Item
+
+export default class BlockLoopOption extends React.Component {
+  constructor(props) {
+    super(props)
+    this.curEventNodeId = newEventStores.curEventNodeId
+    this.state = {
+      option: this.getLoopOption(props.bid)
+    }
+
+    this.onEventChange = this.onEventChange.bind(this)
+    this.getLoopOption = this.getLoopOption.bind(this)
+    this.getOptionName = this.getOptionName.bind(this)
+    this.onSelectOption = this.onSelectOption.bind(this)
+    this.loopOptions = this.loopOptions.bind(this)
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    return (
+      !shallowEqualImmutable(this.props, nextProps) ||
+      !shallowEqualImmutable(this.state, nextState)
+    )
+  }
+
+  componentDidMount() {
+    this.unsubscribe = newEventStores.listen(this.onEventChange)
+  }
+
+  componentWillUnmount() {
+    this.unsubscribe()
+  }
+
+  componentDidUpdate(preProps) {
+    if (preProps.bid !== this.props.bid) {
+      this.setState({
+        option: this.getLoopOption()
+      })
+    }
+  }
+
+  onEventChange(status) {
+    let obj = {}
+    if (status && status.types) {
+      if (
+        status.types.includes('curEventNodeId') &&
+        this.curEventNodeId !== status.data.curEventNodeId
+      ) {
+        this.curEventNodeId = status.data.curEventNodeId
+      }
+      if (
+        status.types.includes('updateBlockProp') &&
+        this.props.bid === status.data.affectBlockId &&
+        status.data.prop === 'op'
+      ) {
+        let option = this.getLoopOption()
+        if (this.state.option !== option) {
+          obj.option = option
+        }
+      }
+    }
+    if (Object.keys(obj).length > 0) {
+      this.setState(obj)
+    }
+  }
+
+  getLoopOption(bid) {
+    let _bid = bid || this.props.bid
+    let nodeId = this.props.eventNodeId || this.curEventNodeId
+    let block = getEventBlockByBid(_bid, nodeId)
+    if (block) {
+      return block.op || BLOCK_LOOP_TYPE.FOR
+    }
+    return BLOCK_LOOP_TYPE.FOR
+  }
+
+  getOptionName() {
+    let option = this.state.option
+    return i18n.t('EventView.jenga_loop_block.' + option)
+  }
+
+  onSelectOption(result) {
+    if (result) {
+      let option = result.key
+      this.setState(
+        {
+          option: option
+        },
+        () => {
+          newEventActions.updateBlockProp({
+            blockId: this.props.bid,
+            prop: 'op',
+            value: option
+          })
+        }
+      )
+    }
+  }
+
+  loopOptions() {
+    return loopOptions.slice(0, 2)
+  }
+
+  render() {
+    let loopOptsMenu = () => {
+      let curLoopOptions = this.loopOptions()
+      return (
+        <Menu className="loop-option-menu" onClick={this.onSelectOption}>
+          {!curLoopOptions || curLoopOptions.length === 0
+            ? null
+            : curLoopOptions.map(opt => {
+                return (
+                  <MenuItem
+                    key={opt}
+                    className={cls({
+                      selected: this.state.option === opt
+                    })}
+                  >
+                    {i18n.t('EventView.jenga_loop_block.' + opt)}
+                  </MenuItem>
+                )
+              })}
+        </Menu>
+      )
+    }
+
+    return (
+      <div className="loop-option f--hlc">
+        <BlockEnable
+          bid={this.props.bid}
+          isEnable={this.props.isEnable}
+          eventNodeId={this.props.eventNodeId}
+        />
+        <Dropdown
+          trigger={['click']}
+          getPopupContainer={triggerNode => triggerNode.parentNode}
+          overlayClassName={'loop-option-dropdown'}
+          overlay={loopOptsMenu()}
+        >
+          <div className="loop-select f--hlc">
+            <input
+              value={this.getOptionName()}
+              readOnly={true}
+              className="ant-dropdown-input"
+              placeholder={i18n.t('EventView.jenga_loop_block.selectLoop')}
+            />
+            <div className="ant-dropdown-icon" />
+          </div>
+        </Dropdown>
+      </div>
+    )
+  }
+}