lianghuang 4 éve
szülő
commit
317ce7313e

+ 152 - 0
coms/jenga/block/BlockLoop.jsx

@@ -0,0 +1,152 @@
+import React from 'react'
+import LazyLoad from 'react-lazyload'
+import cls from 'classnames'
+import Block from './base/Block'
+import BlockChildren from './base/BlockChildren'
+import BlockOrder from './base/BlockOrder'
+import BlockDel from './base/BlockDel'
+import BlockCite from './base/BlockCite'
+import BlockMemo from './base/BlockMemo'
+import BlockLoopOption from './base/loop/BlockLoopOption'
+import BlockLoopCustom from './base/loop/BlockLoopCustom'
+
+export default class BlockLoop extends Block {
+  constructor(props) {
+    super(props)
+  }
+
+  render() {
+    let jengaContent = document.getElementsByClassName('jenga-content')[0]
+
+    let getRelChildComs = () => {
+      return this.state.hasChildren ? (
+        <React.Fragment>
+          {this.state.isExpand ? (
+            <span
+              className={cls(
+                'index-line',
+                'index-main-line',
+                'index-line-' + this.getNextLineColor()
+              )}
+            />
+          ) : null}
+          <button
+            className={cls(
+              'btn-clear btn-toggle-sub btn-expand',
+              'btn-toggle-sub-' +
+                (this.state.isExpand ? 'expand-' : 'collapse-') +
+                this.getLineColor()
+            )}
+            onClick={this.onToggleBlockExpand}
+          />
+        </React.Fragment>
+      ) : (
+        <div
+          className={cls(
+            'btn-clear btn-toggle-sub btn-toggle-sub-empty',
+            'btn-toggle-sub-empty-' + this.getLineColor()
+          )}
+        />
+      )
+    }
+
+    return (
+      <LazyLoad height={26} overflow={true} scrollContainer={jengaContent}>
+        <div className="block-wrap block-wrap-loop f--h">
+          <span
+            className={cls('index-line', 'index-line-' + this.getLineColor(), {
+              'index-last-line': this.props.isLast,
+              'index-virtual-layer': this.props.virtualLayer
+            })}
+            style={this.indexLeftStyle()}
+          />
+          <div
+            className={cls('flex-1', {
+              'flex-wrap-disabled': !this.state.isEnable
+            })}
+          >
+            <div
+              className={cls('block-item block-loop f--h', {
+                'block-active': this.state.isActive
+              })}
+              // onDragOver={this.onDragOver}
+              // onDrop={this.onDrop}
+              onClick={this.onSelectBlock}
+              onContextMenu={this.onContextMenu}
+            >
+              <BlockDel
+                isActive={this.state.isActive}
+                layer={this.props.layer}
+                bid={this.props.bid}
+              />
+              <BlockOrder
+                layer={this.props.layer}
+                bid={this.props.bid}
+                eventNodeId={this.props.eventNodeId}
+                extraGapGroupLayer={this.extraGapGroupLayer()}
+              />
+              <div
+                className="block-gap"
+                style={{
+                  left: -this.getGapLeft() + 'px',
+                  padding: '0 ' + this.getGapLeft() / 2 + 'px'
+                }}
+              />
+              {getRelChildComs()}
+              <div
+                id={'block-main-' + this.props.bid}
+                className="block-main flex-1 f--h"
+                style={this.blockMainStyle()}
+                draggable={true}
+                // onDragStart={this.onDragStart}
+                // onDragEnd={this.onDragEnd}
+                onClick={this.onToggleParamsVisible}
+              >
+                <div
+                  id={'over-cover-' + this.props.bid}
+                  className="over-cover"
+                />
+                <BlockLoopOption
+                  bid={this.props.bid}
+                  isEnable={this.state.isEnable}
+                  eventNodeId={this.props.eventNodeId}
+                />
+                <BlockLoopCustom
+                  bid={this.props.bid}
+                  layer={this.props.layer}
+                  groupLayer={this.props.groupLayer}
+                  nestGroupLayer={this.props.nestGroupLayer}
+                  extraGapGroupLayer={this.extraGapGroupLayer()}
+                  eventNodeId={this.props.eventNodeId}
+                />
+              </div>
+              <BlockCite
+                bid={this.props.bid}
+                eventNodeId={this.props.eventNodeId}
+              />
+              <BlockMemo
+                className="block-loop-memo"
+                active={this.state.isActive}
+                bid={this.props.bid}
+                eventNodeId={this.props.eventNodeId}
+              />
+            </div>
+            {this.state.isExpand ? (
+              <BlockChildren
+                loops={this.getLoops()}
+                layer={this.props.layer + 1}
+                groupLayer={this.props.groupLayer}
+                nestGroupLayer={0}
+                offspringInNestGroupLayer={this.props.nestGroupLayer}
+                extraGapGroupLayer={this.extraGapGroupLayer()}
+                parentBid={this.props.bid}
+                eventNodeId={this.props.eventNodeId}
+                visibleBlockIds={this.props.visibleBlockIds}
+              />
+            ) : null}
+          </div>
+        </div>
+      </LazyLoad>
+    )
+  }
+}

+ 1 - 7
coms/jenga/block/base/con/BlockConConItem.jsx

@@ -19,7 +19,6 @@ export default class BlockConConItem extends ConItem {
     this.state.conOptions = conOptions
 
     this.onChangeConOption = this.onChangeConOption.bind(this)
-    this.onDeleteCon = this.onDeleteCon.bind(this)
 
     this.isSelectedOption = this.isSelectedOption.bind(this)
 
@@ -90,11 +89,6 @@ export default class BlockConConItem extends ConItem {
     }
   }
 
-  onDeleteCon() {
-    this.props.onDelBlockCon({ index: this.props.index, resetLast: true })
-    this.props.onConsChange()
-  }
-
   isSelectedOption(option) {
     return this.state.option === option
   }
@@ -202,7 +196,7 @@ export default class BlockConConItem extends ConItem {
     }
 
     return (
-      <div className={cls('con-item f--hlc')}>
+      <div className="con-item f--hlc">
         {this.props.index === 0 ? (
           <React.Fragment>
             <div className="item-enable" />

+ 11 - 55
coms/jenga/block/base/con/BlockConCons.jsx

@@ -1,7 +1,12 @@
 import React from 'react'
 import newEventActions from 'actions/newEvent'
 import treeStores from 'stores/tree'
-import { getEventBlockByBid, emptyCon } from 'stores/funcs/newEvent'
+import {
+  getEventBlockByBid,
+  emptyCon,
+  genConObj,
+  consAstToArray
+} from 'stores/funcs/newEvent'
 import newEventStores from 'stores/newEvent'
 import { cpJson, shallowEqualImmutable } from 'utils/utils'
 import { BLOCK_OP_TYPE } from 'const/const'
@@ -61,15 +66,6 @@ export default class BlockConCons extends React.Component {
     }
   }
 
-  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
@@ -79,51 +75,11 @@ export default class BlockConCons extends React.Component {
       (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)
-        }
-      }
+      result = consAstToArray(conObj)
     }
     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)
@@ -155,13 +111,13 @@ export default class BlockConCons extends React.Component {
             args: []
           }
           orItem.forEach(conItem => {
-            let conObj = this.genConObj(conItem)
+            let conObj = genConObj(conItem)
             andObj.args.push(conObj)
           })
           consAst.args.push(andObj)
         } else {
           // 单个条件
-          let conObj = this.genConObj(orItem[0])
+          let conObj = genConObj(orItem[0])
           consAst.args.push(conObj)
         }
       })
@@ -173,12 +129,12 @@ export default class BlockConCons extends React.Component {
       if (andArray.length > 1) {
         // 多个条件
         andArray.forEach(conItem => {
-          let conObj = this.genConObj(conItem)
+          let conObj = genConObj(conItem)
           consAst.args.push(conObj)
         })
       } else {
         // 单个条件
-        consAst = this.genConObj(andArray[0])
+        consAst = genConObj(andArray[0])
       }
     }
 

+ 153 - 0
coms/jenga/block/base/loop/BlockLoopConItem.jsx

@@ -0,0 +1,153 @@
+import React from 'react'
+import ConItem from '../ConItem'
+import { Menu, Dropdown } from 'antd'
+import cls from 'classnames'
+import { layerWidth } from 'stores/funcs/newEvent'
+import FormulaEditor from 'src/components/common/formulaEditor/EventFormulaEditor'
+
+const MenuItem = Menu.Item
+
+export default class BlockLoopConItem extends ConItem {
+  constructor(props) {
+    super(props)
+  }
+
+  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 =
+      224.5 - ((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'
+    }
+  }
+
+  render() {
+    let conFlagsMenu = () => {
+      return (
+        <Menu className="loop-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="loop-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="con-item f--hlc">
+        {this.props.index === 0 ? (
+          <React.Fragment>
+            <div className="item-enable" />
+            <div className="item-flag">{this.getConItemFlagName()}</div>
+          </React.Fragment>
+        ) : (
+          <React.Fragment>
+            <div className="item-enable" />
+            <Dropdown
+              trigger={['click']}
+              getPopupContainer={triggerNode => triggerNode.parentNode}
+              overlayClassName={'loop-con-flag-dropdown'}
+              overlay={conFlagsMenu()}
+            >
+              <div className="loop-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="loop-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={'loop-con-operator-dropdown'}
+          overlay={conOperatorsMenu()}
+        >
+          <div className="loop-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="loop-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 btn-loop-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>
+    )
+  }
+}

+ 292 - 0
coms/jenga/block/base/loop/BlockLoopCustom.jsx

@@ -0,0 +1,292 @@
+import React from 'react'
+import i18n from 'i18next'
+import newEventActions from 'actions/newEvent'
+import newEventStores from 'stores/newEvent'
+import treeStores from 'stores/tree'
+import {
+  getEventBlockByBid,
+  emptyCon,
+  genConObj,
+  consAstToArray
+} from 'stores/funcs/newEvent'
+import { cpJson, shallowEqualImmutable } from 'utils/utils'
+import { consFormulaShow } from 'utils/commons/ctrlHide'
+import { BLOCK_OP_TYPE } from 'const/const'
+import BlockLoopConItem from './BlockLoopConItem'
+import FormulaEditor from 'src/components/common/formulaEditor/EventFormulaEditor'
+
+export default class BlockLoopCustom extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {
+      option: this.getLoopOption(props.bid),
+      custom: this.getLoopCustom(this.getLoopOption(props.bid), props.bid)
+    }
+
+    this.onEventChange = this.onEventChange.bind(this)
+    this.onChangeTimes = this.onChangeTimes.bind(this)
+    this.getLoopOption = this.getLoopOption.bind(this)
+    this.getLoopCustom = this.getLoopCustom.bind(this)
+    this.getCustomCom = this.getCustomCom.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({
+        option: this.getLoopOption(),
+        custom: this.getLoopCustom(this.getLoopOption())
+      })
+    }
+  }
+
+  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.getLoopOption()
+        if (this.state.option !== option) {
+          obj.option = option
+          obj.custom = this.getLoopCustom(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)
+    }
+  }
+
+  genForCons(blockId, value) {
+    return {
+      op: 'let',
+      val: [blockId, 'int'],
+      args: [{ op: 'step', args: [{ op: 'var', expVal: value, args: [] }] }]
+    }
+  }
+
+  onChangeTimes(value) {
+    this.setState(
+      {
+        custom: value
+      },
+      () => {
+        let forCons = this.genForCons(this.props.bid, value)
+        newEventActions.updateBlockProp({
+          blockId: this.props.bid,
+          prop: 'cons',
+          value: forCons
+        })
+      }
+    )
+  }
+
+  getLoopOption(bid) {
+    let _bid = bid || this.props.bid
+    let nodeId = this.props.eventNodeId || newEventStores.curEventNodeId
+    let block = getEventBlockByBid(_bid, nodeId)
+    if (block) {
+      return block.op || BLOCK_OP_TYPE.FOR
+    }
+    return BLOCK_OP_TYPE.FOR
+  }
+
+  getLoopCustom(option, bid) {
+    let _bid = bid || this.props.bid
+    let nodeId = this.props.eventNodeId || newEventStores.curEventNodeId
+    let block = getEventBlockByBid(_bid, nodeId)
+    if (block) {
+      switch (option) {
+        case BLOCK_OP_TYPE.FOR:
+          return (
+            block.args &&
+            block.args[0] &&
+            block.args[0].args &&
+            block.args[0].args[0] === 'step' &&
+            block.args[0].args[0].args &&
+            block.args[0].args[0].args[0].expVar
+          )
+        case BLOCK_OP_TYPE.WHILE:
+          return consAstToArray(block.args[0])
+      }
+    }
+    return null
+  }
+
+  changeConsAst() {
+    // 将cons数组转为ast格式
+    let cons = cpJson(this.state.custom)
+    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 = genConObj(conItem)
+            andObj.args.push(conObj)
+          })
+          consAst.args.push(andObj)
+        } else {
+          // 单个条件
+          let conObj = 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 = genConObj(conItem)
+          consAst.args.push(conObj)
+        })
+      } else {
+        // 单个条件
+        consAst = 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.custom
+    let newEmptyCon = emptyCon()
+    cons.push(newEmptyCon)
+  }
+
+  delBlockCon({ index, resetLast }) {
+    let cons = this.state.custom
+    if (resetLast && cons.length === 1) {
+      cons[0] = emptyCon()
+    } else {
+      cons.splice(index, 1)
+    }
+    if (cons.length === 1) {
+      cons[0].flag = 'and'
+    }
+  }
+
+  getCustomCom() {
+    let forCom = () => {
+      return (
+        <div className="loop-for-wrap f--hlc">
+          <div className="for-tag f--hcc">
+            {i18n.t('EventView.jenga_loop_block.forTag')}
+          </div>
+          <div className="for-times-wrap f--h">
+            <FormulaEditor
+              className="loop-formula-editor loop-for-formula-editor"
+              uid={this.props.bid + BLOCK_OP_TYPE.FOR}
+              bid={this.props.bid}
+              value={this.state.custom}
+              hasCons={consFormulaShow()}
+              onChangeCode={this.onChangeTimes}
+            />
+          </div>
+        </div>
+      )
+    }
+
+    let whileCom = () => {
+      let cons = (
+        <div className="con-items-wrap">
+          {this.state.custom.map((con, index) => {
+            return (
+              <BlockLoopConItem
+                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="loop-while-wrap f--hlc">
+          {!this.state.custom || this.state.custom.length === 0 ? null : cons}
+        </div>
+      )
+    }
+
+    switch (this.state.option) {
+      case BLOCK_OP_TYPE.FOR:
+        return forCom()
+      case BLOCK_OP_TYPE.WHILE:
+        return whileCom()
+    }
+  }
+
+  render() {
+    return <div className="loop-custom flex-1">{this.getCustomCom()}</div>
+  }
+}

+ 3 - 1
coms/jenga/block/base/loop/BlockLoopOption.jsx

@@ -101,7 +101,9 @@ export default class BlockLoopOption extends React.Component {
           newEventActions.updateBlockProp({
             blockId: this.props.bid,
             prop: 'op',
-            value: option
+            value: option,
+            trigger: true,
+            addRecord: true
           })
         }
       )