Browse Source

Merge branch 'develop'

gaozhan 5 năm trước cách đây
mục cha
commit
c7ffb835a4
5 tập tin đã thay đổi với 83 bổ sung1013 xóa
  1. 0 2
      package.json
  2. 42 24
      src/pages/poster/edit/$id$.js
  3. 0 985
      src/pages/poster/edit/index copy.js
  4. 1 1
      src/pages/poster/edit/index.less
  5. 40 1
      yarn.lock

+ 0 - 2
package.json

@@ -10,8 +10,6 @@
     "antd": "^3.23.4",
     "axios": "^0.19.0",
     "dva": "^2.4.1",
-    "fabric": "^3.6.3",
-    "jr-qrcode": "^1.1.4",
     "lodash": "^4.17.15",
     "lodash.clonedeep": "^4.5.0",
     "lodash.throttle": "^4.1.1",

+ 42 - 24
src/pages/poster/edit/$id$.js

@@ -2,6 +2,8 @@
 import React from 'react'
 import { Button, Form, Input, Select, Slider, Switch, InputNumber, Icon, Popover, Modal, message } from 'antd'
 import { Rnd } from 'react-rnd'
+import { ImagePreview } from 'wptpc-design'
+
 import isEmpty from 'lodash/isEmpty'
 // import { connect } from 'dva'
 
@@ -30,7 +32,9 @@ export default class Edit extends React.PureComponent {
     },
     element: [],
     activeObjectIndex: 0,
-    editName: ''
+    editName: '',
+    imgs: [], // 预览图片的url
+    previwVisible: false // 预览框是否展示
   }
 
   componentDidMount () {
@@ -38,8 +42,8 @@ export default class Edit extends React.PureComponent {
     if (this.props.match.params.id) {
       fetchDetail({ id: this.props.match.params.id }).then(res => {
         if (!isEmpty(res.data)) {
-          const { data: { description, name, id, config: [bg, ...element] } } = res
-          this.setState({ name: name, description: description, element: element, id: id, bg: bg })
+          const { data: { description, name, id, preview, config: [bg, ...element] } } = res
+          this.setState({ name: name, description: description, element: element, id: id, bg: bg, imgs: [preview] })
         }
       })
     }
@@ -140,13 +144,22 @@ export default class Edit extends React.PureComponent {
       config: ([bg]).concat(element)
     }).then(res => {
       if (res.code === 0) {
-        message.success('设置成功~')
+        if (!id) {
+          message.success('添加成功~')
+          window.location.href = '/poster/edit/' + res.data.id
+        } else {
+          message.success('设置成功~')
+          this.setState({
+            imgs: [res.data.preview],
+            previwVisible: true
+          })
+        }
       }
     })
   }
 
   render () {
-    const { bg: { w: bgWidth, h: bgHeight, c: bgColor }, activeObjectIndex, element, name, description } = this.state
+    const { bg: { w: bgWidth, h: bgHeight, c: bgColor }, activeObjectIndex, element, name, description, imgs, previwVisible } = this.state
     const activeObject = element[activeObjectIndex] || {}
     const group = element.map(item => item.group)
     const _element = element.map((item, index) => ({ ...item, index: index }))
@@ -154,10 +167,10 @@ export default class Edit extends React.PureComponent {
     return (
       <div id="poster" style={{ height: this.state.posterHeight, display: 'flex', flexDirection: 'column' }}>
         <div className={styles.header}>
-          <span>模版:<Input style={{ width: '120px' }} value={name} onChange={e => this.setState({ name: e.target.value })}/></span>
+          <span>模版:<Input style={{ width: '200px' }} value={name} onChange={e => this.setState({ name: e.target.value })}/></span>
           <span>&emsp;描述:<Input style={{ width: '200px' }} value={description} onChange={e => this.setState({ description: e.target.value })}/></span>
           <span>
-            {/* <Button>预览</Button> */}
+            <Button onClick={() => this.setState({ previwVisible: true })}>预览</Button>
             <Button type="primary" onClick={this.save}>保存</Button>
           </span>
         </div>
@@ -317,8 +330,8 @@ export default class Edit extends React.PureComponent {
                           style={{
                             backgroundRepeat: 'no-repeat',
                             backgroundSize: 'cover',
-                            backgroundImage: `url(${item.logo === 'wpt' || item.logo === 'youjiang'
-                          ? (item.logo === 'wpt' ? 'https://cdn.weipaitang.com/static/20200413eb9effec-3871-ffec3871-cd41-baed9e98535e-W200H200' : 'https://cdn.weipaitang.com/static/202004141c0b320a-9cdc-320a9cdc-d090-e4b5964fee9d-W200H200')
+                            backgroundImage: `url(${item.logo === 'wpt_high' || item.logo === 'youjiang'
+                          ? (item.logo === 'wpt_high' ? 'https://cdn.weipaitang.com/static/20200413eb9effec-3871-ffec3871-cd41-baed9e98535e-W200H200' : 'https://cdn.weipaitang.com/static/202004141c0b320a-9cdc-320a9cdc-d090-e4b5964fee9d-W200H200')
                           : 'https://cdn.weipaitang.com/static/20200413d4263aa5-6423-3aa56423-4e8d-a96e1d9d93f3-W200H200'})`,
                             width: Number.parseFloat(item.size || '0') + 'px',
                             height: Number.parseFloat(item.size || '0') + 'px'
@@ -365,7 +378,7 @@ export default class Edit extends React.PureComponent {
                     </Form.Item>
                     <div style={{ display: 'flex' }}>
                       <Form.Item label="字体大小" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} min={12} max={100} value={activeObject.size} onChange={(value) => this.updateElement(activeObjectIndex, { size: value })} />
+                        <InputNumber style={{ width: 80 }} min={12} max={100} value={activeObject.size} onChange={(value) => this.updateElement(activeObjectIndex, { size: value })} />
                       </Form.Item>
                       <Form.Item label="字体颜色" style={{ flex: 1 }}>
                         <div style={{ width: '60px' }}
@@ -382,18 +395,18 @@ export default class Edit extends React.PureComponent {
                     </div>
                     <div style={{ display: 'flex' }}>
                       <Form.Item label="X偏移" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.dx} onChange={(value) => this.updateElement(activeObjectIndex, { dx: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.dx} onChange={(value) => this.updateElement(activeObjectIndex, { dx: value })}/>
                       </Form.Item>
                       <Form.Item label="Y偏移" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.dy} onChange={(value) => this.updateElement(activeObjectIndex, { dy: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.dy} onChange={(value) => this.updateElement(activeObjectIndex, { dy: value })}/>
                       </Form.Item>
                     </div>
                     <div style={{ display: 'flex' }}>
                       <Form.Item label="文字宽度" style={{ flex: 1 }}>
-                        <InputNumber value={activeObject.width} style={{ width: 60 }} onChange={(value) => this.updateElement(activeObjectIndex, { width: value })}/>
+                        <InputNumber value={activeObject.width} style={{ width: 80 }} onChange={(value) => this.updateElement(activeObjectIndex, { width: value })}/>
                       </Form.Item>
                       <Form.Item label="文字高度" style={{ flex: 1 }}>
-                        <InputNumber value={activeObject.height} style={{ width: 60 }} onChange={(value) => this.updateElement(activeObjectIndex, { height: value })}/>
+                        <InputNumber value={activeObject.height} style={{ width: 80 }} onChange={(value) => this.updateElement(activeObjectIndex, { height: value })}/>
                       </Form.Item>
                     </div>
                     <div style={{ display: 'flex' }}>
@@ -462,23 +475,23 @@ export default class Edit extends React.PureComponent {
                     </Form.Item>
                     <div style={{ display: 'flex' }}>
                       <Form.Item label="X偏移" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.dx} onChange={(value) => this.updateElement(activeObjectIndex, { dx: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.dx} onChange={(value) => this.updateElement(activeObjectIndex, { dx: value })}/>
                       </Form.Item>
                       <Form.Item label="Y偏移" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.dy} onChange={(value) => this.updateElement(activeObjectIndex, { dy: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.dy} onChange={(value) => this.updateElement(activeObjectIndex, { dy: value })}/>
                       </Form.Item>
                     </div>
                     <Form.Item label="LOGO">
                       <Switch
-                        checked={activeObject.logo === 'wpt' || activeObject.logo === 'youjiang'}
-                        onChange={(checked) => this.updateElement(activeObjectIndex, { logo: checked ? 'wpt' : false })}
+                        checked={activeObject.logo === 'wpt_high' || activeObject.logo === 'youjiang'}
+                        onChange={(checked) => this.updateElement(activeObjectIndex, { logo: checked ? 'wpt_high' : false })}
                         checkedChildren="开"
                         unCheckedChildren="关"
                       />
                       {
-                        (activeObject.logo === 'wpt' || activeObject.logo === 'youjiang') && (
+                        (activeObject.logo === 'wpt_high' || activeObject.logo === 'youjiang') && (
                           <Select value={activeObject.logo} style={{ width: 120, marginLeft: '5px' }} onChange={value => this.updateElement(activeObjectIndex, { logo: value })}>
-                            <Option value="wpt">微拍堂</Option>
+                            <Option value="wpt_high">微拍堂</Option>
                             <Option value="youjiang">有匠</Option>
                           </Select>
                         )
@@ -527,18 +540,18 @@ export default class Edit extends React.PureComponent {
                     </Form.Item>
                     <div style={{ display: 'flex' }}>
                       <Form.Item label="宽" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.w} onChange={(value) => this.updateElement(activeObjectIndex, { w: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.w} onChange={(value) => this.updateElement(activeObjectIndex, { w: value })}/>
                       </Form.Item>
                       <Form.Item label="高" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.h} onChange={(value) => this.updateElement(activeObjectIndex, { h: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.h} onChange={(value) => this.updateElement(activeObjectIndex, { h: value })}/>
                       </Form.Item>
                     </div>
                     <div style={{ display: 'flex' }}>
                       <Form.Item label="X偏移" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.dx} onChange={(value) => this.updateElement(activeObjectIndex, { dx: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.dx} onChange={(value) => this.updateElement(activeObjectIndex, { dx: value })}/>
                       </Form.Item>
                       <Form.Item label="Y偏移" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 60 }} value={activeObject.dy} onChange={(value) => this.updateElement(activeObjectIndex, { dy: value })}/>
+                        <InputNumber style={{ width: 80 }} value={activeObject.dy} onChange={(value) => this.updateElement(activeObjectIndex, { dy: value })}/>
                       </Form.Item>
                     </div>
                     <div style={{ display: 'flex' }}>
@@ -616,6 +629,11 @@ export default class Edit extends React.PureComponent {
         >
           <Input value={this.state.editName.replace(/#/g, '')} onChange={e => this.setState({ editName: e.target.value })} />
         </Modal>
+        <ImagePreview
+          images={imgs}
+          visible={previwVisible}
+          onClose={() => { this.setState({ previwVisible: false }) }}
+        />
       </div>
     )
   }

+ 0 - 985
src/pages/poster/edit/index copy.js

@@ -1,985 +0,0 @@
-/* eslint-disable camelcase */
-import React from 'react'
-import { Button, Form, Input, Select, Slider, Switch, InputNumber } from 'antd'
-import fabric from 'fabric'
-import cloneDeep from 'lodash.clonedeep'
-import throttle from 'lodash.throttle'
-import RcColorPicker from 'rc-color-picker'
-import 'rc-color-picker/assets/index.css'
-import jrQrcode from 'jr-qrcode'
-
-import { optionArr, newOptionArr } from './util/data.js'
-
-import styles from './index.less'
-
-fabric = fabric.fabric
-const { Option } = Select
-
-const QRErrorCorrectLevel = {
-  L: 1,
-  M: 0,
-  Q: 3,
-  H: 2
-}
-
-const _config = {
-  canvasState: [],
-  currentStateIndex: -1,
-  undoStatus: false,
-  redoStatus: false,
-  undoFinishedStatus: 1,
-  redoFinishedStatus: 1
-}
-
-export default class Edit extends React.PureComponent {
-  state={
-    name: '模版一',
-    description: '描述',
-    canvas: {},
-
-    currentOptionArr: cloneDeep(newOptionArr), // 当前可设置的数组的值
-    optionArr: cloneDeep(newOptionArr) // 当前可设置的数组的值
-    // currentObjectType: 'text' // 当前要添加对象的类型
-  }
-
-  activeObject={}
-  currentOptionArr=cloneDeep(newOptionArr)
-
-  componentDidMount () {
-    this.canvas_sprite = new fabric.Canvas('canvas', {
-      ...this.state.currentOptionArr[0].css,
-      backgroundColor: this.state.currentOptionArr[0].css.background
-    })
-    this.addEventListener()
-  }
-
-  async addShape (index, action) {
-    const currentOptionArr = this.state.currentOptionArr
-    const { type } = currentOptionArr[index]
-
-    let Shape
-    switch (type) {
-      case 'text':
-        Shape = await this.addTextObject(index, action)
-        break
-      case 'image':
-        Shape = await this.addImageObject(index, action)
-        break
-      case 'qrcode':
-        Shape = await this.addQrcodeObject(index, action)
-        break
-      default:
-        break
-    }
-    this.canvas_sprite.setActiveObject(Shape)
-    this.activeObject = Shape
-    // this.setState({
-    //   visible: true
-    // })
-    this.canvas_sprite.add(Shape)
-    if (action !== 'update') {
-      this.changeActiveObjectValue()
-    }
-  }
-
-  async addTextObject (index, action) {
-    let currentOptionArr
-    if (action === 'update') {
-      currentOptionArr = this.state.currentOptionArr
-    } else {
-      currentOptionArr = this.currentOptionArr
-    }
-    const { css } = currentOptionArr[index]
-    let {
-      width,
-      text,
-      color,
-      fontSize,
-      left,
-      top,
-      fontWeight,
-      fontFamily,
-      padding,
-      textDecoration,
-      borderRadius,
-      borderWidth,
-      borderColor,
-      rotate,
-      // align,
-      shadow,
-      lineHeight,
-      textAlign,
-      maxLines,
-      textStyle,
-      background
-    } = css
-    width = width / 1
-    left = left / 1
-    top = top / 1
-    borderRadius = borderRadius / 1
-    borderWidth = borderWidth / 1
-    rotate = rotate / 1
-    fontSize = fontSize / 1
-    maxLines = maxLines / 1
-    padding = 0
-    lineHeight = lineHeight / 1 // 和painter调试得出的值
-    shadow = shadow
-      .trim()
-      .split(/\s+/)
-      .join(' ')
-    // let Shape
-    const config = {
-      width, // 文字的高度随行高
-      fill: color,
-      fontWeight,
-      left: 0, // 距离画布左侧的距离,单位是像素
-      top: 0,
-      fontSize, // 文字大小
-      fontFamily,
-      padding,
-      [textDecoration]: true,
-      textAlign,
-      textStyle,
-      shadow,
-      myshadow: shadow,
-      splitByGrapheme: true, // 文字换行
-      lineHeight,
-      editable: true,
-      maxLines: maxLines,
-      textDecoration: textDecoration,
-      lockScalingY: true,
-      originX: 'center',
-      originY: 'center'
-    }
-    // 镂空
-    // if (textStyle === 'stroke') {
-    //   config = {
-    //     ...config,
-    //     stroke: color,
-    //     fill: 'rgba(0,0,0)'
-    //   }
-    // }
-
-    const textBox = new fabric.Textbox(text, config)
-    textBox.toObject = (function (toObject) {
-      return function () {
-        return fabric.util.object.extend(toObject.call(this), {
-          maxLines,
-          textDecoration,
-          textStyle
-        })
-      }
-    })(textBox.toObject)
-    // 通过最大行高计算高度,并删除多余文字,多出文字..表示,三个会换行
-    if (textBox.textLines.length > maxLines) {
-      let text = ''
-      for (let index = 0; index < maxLines; index++) {
-        const element = textBox.textLines[index]
-        if (index === maxLines - 1) {
-          text = text + element + '...'
-        } else {
-          text += element
-        }
-      }
-      textBox.set({
-        text
-      })
-      if (textBox.textLines.length > maxLines) {
-        let text = ''
-        for (let index = 0; index < maxLines; index++) {
-          const element = textBox.textLines[index]
-          if (index === maxLines - 1) {
-            text = text + element.substring(0, element.length - 3) + '...'
-          } else {
-            text += element
-          }
-        }
-        textBox.set({
-          text
-        })
-      }
-    }
-    const height = textBox.height / 1 + (textBox.lineHeight / 1 - 1) * textBox.fontSize + padding * 2
-    left = css.left - padding + borderWidth
-    top = css.top - padding + borderWidth
-    textBox.set({
-      top: -((textBox.lineHeight / 1 - 1) * textBox.fontSize) / 2
-    })
-
-    // const Rect = new fabric.Rect({
-    //   width: width + borderWidth,
-    //   height: height + borderWidth,
-    //   left: 0, // 距离画布左侧的距离,单位是像素
-    //   top: 0,
-    //   originX: 'center',
-    //   originY: 'center',
-    //   // padding,
-    //   rx: borderRadius,
-    //   strokeWidth: borderWidth / 1,
-    //   stroke: borderColor,
-    //   fill: background,
-    //   shadow,
-    //   selectable: false
-    // })
-    // this.canvas_sprite.add(Rect);
-    // let gradientOption = ''
-    // if (GD.api.isGradient(background)) {
-    //   alert(1)
-    //   gradientOption = GD.api.doGradient(background, width, height)
-    // } else {
-    //   alert(2)
-    // }
-    // if (gradientOption) Rect.setGradient('fill', gradientOption)
-    const Shape = new fabric.Group([], {
-      width: width + borderWidth,
-      height: height + borderWidth,
-      left: left + width / 2, // 距离画布左侧的距离,单位是像素
-      top: top + height / 2,
-      angle: rotate,
-      mytype: 'textGroup',
-      oldText: text,
-      originX: 'center',
-      originY: 'center',
-      rx: borderRadius,
-      strokeWidth: borderWidth / 1,
-      stroke: borderColor,
-      fill: background,
-      shadow,
-      myshadow: shadow,
-      lockScalingY: true
-    })
-    // Shape.add(Rect)
-    Shape.add(textBox)
-
-    Shape.toObject = (function (toObject) {
-      return function () {
-        return fabric.util.object.extend(toObject.call(this), {
-          mytype: 'textGroup',
-          oldText: text,
-          rx: borderRadius,
-          myshadow: shadow
-        })
-      }
-    })(Shape.toObject)
-    return Shape
-  }
-
-  async addImageObject (index, action) {
-    let currentOptionArr
-    if (action === 'update') {
-      currentOptionArr = this.state.currentOptionArr
-    } else {
-      currentOptionArr = this.currentOptionArr
-    }
-    const { css } = currentOptionArr[index]
-    let {
-      width,
-      height,
-      left,
-      top,
-      borderRadius,
-      borderWidth,
-      borderColor,
-      background,
-      rotate,
-      // align,
-      shadow,
-      mode,
-      url
-    } = css
-    width = width / 1
-    height = height / 1
-    left = left / 1
-    top = top / 1
-    borderRadius = borderRadius / 1
-    borderWidth = borderWidth / 1
-    rotate = rotate / 1
-    shadow = shadow
-      .trim()
-      .split(/\s+/)
-      .join(' ')
-    const Shape = await this.loadImageUrl(url)
-    const imgWidth = Shape.width
-    const imgHeight = Shape.height
-    Shape.set({
-      url,
-      // align,
-      mode,
-      shadow,
-      originX: 'center',
-      originY: 'center'
-    })
-    if (mode === 'scaleToFill') {
-      Shape.set({
-        width: imgWidth,
-        height: imgHeight,
-        scaleX: width / imgWidth,
-        scaleY: height / imgHeight,
-        oldScaleX: width / imgWidth,
-        oldScaleY: height / imgHeight
-      })
-      Shape.clipPath = new fabric.Rect({
-        width,
-        height,
-        originX: 'center',
-        originY: 'center',
-        rx: borderRadius,
-        scaleX: imgWidth / width,
-        scaleY: imgHeight / height
-      })
-    } else if (mode === 'auto') {
-      // 忽略高度会自适应宽度,等比缩放图片
-      Shape.set({
-        width: imgWidth,
-        height: imgHeight,
-        scaleX: width / imgWidth,
-        scaleY: width / imgWidth,
-        oldScaleX: width / imgWidth,
-        oldScaleY: height / imgHeight
-      })
-      Shape.clipPath = new fabric.Rect({
-        width,
-        height,
-        originX: 'center',
-        originY: 'center',
-        rx: borderRadius,
-        scaleX: imgWidth / width,
-        scaleY: imgHeight / height
-      })
-    } else if (mode === 'aspectFill') {
-      Shape.clipPath = new fabric.Rect({
-        width: width / 1,
-        height: height / 1,
-        originX: 'center',
-        originY: 'center',
-        rx: borderRadius / 1
-      })
-      Shape.set({
-        width,
-        height
-      })
-    }
-    const group = new fabric.Group([Shape], {
-      left: left + width / 2 + borderWidth,
-      top: top + height / 2 + borderWidth,
-      width: width + borderWidth,
-      height: height + borderWidth,
-      rx: borderRadius / 1,
-      strokeWidth: borderWidth / 1,
-      stroke: borderColor,
-      fill: background,
-      angle: rotate,
-      shadow,
-      myshadow: shadow,
-      originX: 'center',
-      originY: 'center',
-      mytype: 'image',
-      mode,
-      url,
-      lockUniScaling: true // 只能等比缩放
-    })
-    // 添加边框
-    group.add(
-      new fabric.Rect({
-        width: width + borderWidth,
-        height: height + borderWidth,
-        left: 0,
-        top: 0,
-        originX: 'center',
-        originY: 'center',
-        // padding,
-        rx: borderRadius + borderWidth / 2,
-        strokeWidth: borderWidth / 1,
-        stroke: borderColor,
-        fill: 'rgba(0,0,0,0)',
-        shadow,
-        selectable: false
-      })
-    )
-    group.toObject = (function (toObject) {
-      return function () {
-        return fabric.util.object.extend(toObject.call(this), {
-          mytype: 'image',
-          mode,
-          url,
-          rx: borderRadius + borderWidth / 2,
-          oldScaleX: width / imgWidth,
-          oldScaleY: height / imgHeight,
-          myshadow: shadow
-        })
-      }
-    })(group.toObject)
-    // console.log('group', group);
-    return group
-  }
-
-  async addQrcodeObject (index, action) {
-    let currentOptionArr
-    if (action === 'update') {
-      currentOptionArr = this.state.currentOptionArr
-    } else {
-      currentOptionArr = this.currentOptionArr
-    }
-    const { css } = currentOptionArr[index]
-    let {
-      width,
-      left,
-      top,
-      color,
-      borderRadius,
-      // borderWidth,
-      // borderColor,
-      background,
-      rotate,
-      url
-      // align,
-    } = css
-    width = width / 1
-    left = left / 1 + width / 2
-    top = top / 1 + width / 2
-    rotate = rotate / 1
-    const imgBase64 = jrQrcode.getQrBase64(url, {
-      padding: 0, // 二维码四边空白(默认为10px)
-      width: width / 1, // 二维码图片宽度(默认为256px)
-      height: width / 1, // 二维码图片高度(默认为256px)
-      correctLevel: QRErrorCorrectLevel.H, // 二维码容错level(默认为高)
-      reverse: false, // 反色二维码,二维码颜色为上层容器的背景颜色
-      background: background, // 二维码背景颜色(默认白色)
-      foreground: color // 二维码颜色(默认黑色)
-    })
-    const Shape = await this.loadImageUrl(imgBase64)
-    Shape.set({
-      url,
-      width: width / 1,
-      height: width / 1,
-      left,
-      top,
-      color,
-      background,
-      rx: borderRadius / 1,
-      // strokeWidth: borderWidth / 1,
-      // stroke: borderColor,
-      // align,
-      angle: rotate / 1,
-      lockUniScaling: true, // 只能等比缩放
-      originX: 'center',
-      originY: 'center',
-      mytype: 'qrcode'
-    })
-    Shape.clipPath = new fabric.Rect({
-      width,
-      height: width / 1,
-      originX: 'center',
-      originY: 'center',
-      rx: borderRadius,
-      angle: rotate / 1
-    })
-    Shape.toObject = (function (toObject) {
-      return function () {
-        return fabric.util.object.extend(toObject.call(this), {
-          mytype: 'qrcode',
-          url,
-          color,
-          background,
-          rx: borderRadius / 1
-        })
-      }
-    })(Shape.toObject)
-    return Shape
-  }
-
-  loadImageUrl = (imgUrl) => {
-    return new Promise(resolve => {
-      fabric.Image.fromURL(imgUrl, function (oImg) {
-        resolve(oImg)
-      })
-    })
-  }
-
-  changeActiveObjectValue = () => {
-    const type = this.activeObject.mytype
-    if (!type) return
-    // this.setState({
-    //   visible: true
-    // })
-    const item2 = this.activeObject
-    const width = `${(item2.width - item2.strokeWidth) * item2.scaleX}`
-    const height = `${(item2.height - item2.strokeWidth) * item2.scaleY}`
-    /* let left = `${(item2.left / item2.scaleY - (item2.width - item2.strokeWidth) / 2 - item2.strokeWidth).toFixed(2)}`;
-    let top = `${(item2.top / item2.scaleY - (item2.height - item2.strokeWidth) / 2 - item2.strokeWidth).toFixed(2)}`; */
-    const left = `${(item2.left - width / 2).toFixed(2)}`
-    const top = `${(item2.top - height / 2).toFixed(2)}`
-
-    let css = {
-      width,
-      height,
-      left,
-      top,
-      color: `${item2.color}`,
-      background: `${item2.fill}`,
-      rotate: `${item2.angle}`,
-      borderRadius: `${item2.rx * item2.scaleY}`,
-      borderWidth: `${item2.strokeWidth * item2.scaleY}`,
-      borderColor: `${item2.stroke}`,
-      shadow: `${item2.myshadow}`
-    }
-    let index = ''
-    switch (type) {
-      case 'textGroup':
-        index = 1
-        item2._objects.forEach(ele => {
-          // const css2 = {
-          //   text: '',
-          //   width,
-          //   maxLines: '',
-          //   lineHeight: '',
-          //   left,
-          //   top,
-          //   color: `${item2.color}`,
-          //   background: `${item2.fill}`,
-          //   fontSize: '',
-          //   fontWeight: '',
-          //   textDecoration: '',
-          //   rotate: `${item2.angle}`,
-          //   // padding: 0,
-          //   borderRadius: `${item2.rx * item2.scaleY}`,
-          //   borderWidth: `${item2.strokeWidth * item2.scaleY}`,
-          //   borderColor: `${item2.stroke}`,
-          //   shadow: `${item2.shadow}`,
-          //   textStyle: '',
-          //   textAlign: '',
-          //   fontFamily: ''
-          // }
-          // if (ele.type === 'rect') {
-          // } else {
-
-          // }
-          css = {
-            // text: '',
-            width,
-            // maxLines: '',
-            // lineHeight: '',
-            left,
-            top,
-            // color: `${item2.color}`,
-            background: `${item2.fill}`,
-            // fontSize: '',
-            // fontWeight: '',
-            // textDecoration: '',
-            rotate: `${item2.angle}`,
-            // padding: 0,
-            borderRadius: `${item2.rx * item2.scaleY}`,
-            borderWidth: `${item2.strokeWidth * item2.scaleY}`,
-            borderColor: `${item2.stroke}`,
-            // shadow: `${item2.shadow}`,
-            // textStyle: '',
-            // textAlign: '',
-            // fontFamily: '',
-            text: `${item2.oldText}`,
-            maxLines: `${ele.maxLines}`,
-            lineHeight: `${ele.lineHeight}`,
-            color: ele.fill,
-            // padding: `${ele.padding}`,
-            fontSize: `${ele.fontSize}`,
-            fontWeight: `${ele.fontWeight}`,
-            textStyle: `${ele.textStyle}`,
-            textDecoration: `${ele.textDecoration === 'linethrough' ? 'line-through' : ele.textDecoration}`,
-            fontFamily: `${ele.fontFamily}`,
-            textAlign: `${ele.textAlign}`,
-            shadow: `${item2.myshadow}`
-          }
-        })
-        break
-      case 'image':
-        index = 2
-        delete css.color
-        delete css.background
-        css = {
-          url: item2.url,
-          ...css,
-          mode: `${item2.mode}`,
-          shadow: `${item2.myshadow}`
-        }
-        break
-      case 'qrcode':
-        index = 3
-        delete css.height
-        delete css.borderWidth
-        delete css.borderColor
-        delete css.shadow
-        css = {
-          url: item2.url,
-          ...css,
-          color: item2.color,
-          background: item2.background
-        }
-        break
-      default:
-        break
-    }
-    const currentOptionArr = cloneDeep(this.state.currentOptionArr)
-    currentOptionArr[index].css = css
-    this.setState({
-      currentOptionArr
-    })
-  }
-
-  handleChange = (index, key, value) => {
-    const currentOptionArr = cloneDeep(this.state.currentOptionArr)
-    currentOptionArr[index].css[key] = value
-    this.setState(
-      {
-        currentOptionArr
-      },
-      () => {
-        this.updateObject()
-      }
-    )
-  }
-
-  async updateObject () {
-    const type = this.activeObject.mytype
-    this.canvas_sprite.remove(this.activeObject)
-    switch (type) {
-      case 'textGroup':
-        await this.addShape(1, 'update')
-        break
-      case 'image':
-        await this.addShape(2, 'update')
-        break
-      case 'qrcode':
-        await this.addShape(3, 'update')
-        break
-      default:
-        break
-    }
-    this.canvas_sprite.renderAll()
-  }
-
-  addEventListener = () => {
-    const throttlechangeActiveObjectValue = throttle(this.changeActiveObjectValue, 100)
-    this.canvas_sprite.on('object:moving', (e) => {
-      console.log('object:moving')
-      var obj = e.target
-      // if object is too big ignore
-      if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {
-        return
-      }
-      obj.setCoords()
-      // top-left  corner
-      if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
-        obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top)
-        obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left)
-      }
-      // bot-right corner
-      if (
-        obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height ||
-        obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width
-      ) {
-        obj.top = Math.min(
-          obj.top,
-          obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top
-        )
-        obj.left = Math.min(
-          obj.left,
-          obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left
-        )
-      }
-
-      throttlechangeActiveObjectValue()
-    })
-    this.canvas_sprite.on('object:scaling', (e) => {
-      console.log('object:scaling')
-      var obj = e.target
-      // if object is too big ignore
-      if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {
-        return
-      }
-      obj.setCoords()
-      // top-left  corner
-      if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
-        obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top)
-        obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left)
-      }
-      // bot-right corner
-      if (
-        obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height ||
-        obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width
-      ) {
-        obj.top = Math.min(
-          obj.top,
-          obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top
-        )
-        obj.left = Math.min(
-          obj.left,
-          obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left
-        )
-      }
-      throttlechangeActiveObjectValue()
-    })
-    this.canvas_sprite.on('mouse:down', (e) => {
-      console.log('mouse:down')
-      if (e.target) {
-        this.activeObject = e.target
-        this.changeActiveObjectValue()
-      }
-    })
-    // 解决放大缩小元素位置不对的问题
-    this.canvas_sprite.on('object:scaled', (e) => {
-      console.log('object:scaled')
-      if (e.target) {
-        this.activeObject = e.target
-        this.updateObject()
-      }
-    })
-    this.canvas_sprite.on('object:modified', () => {
-      console.log('object:modified')
-      this.updateCanvasState()
-    })
-
-    this.canvas_sprite.on('object:added', () => {
-      console.log('object:added')
-      this.updateCanvasState()
-    })
-  }
-
-  updateCanvasState = () => {
-    const canvas_sprite = this.canvas_sprite
-    if (_config.undoStatus === false && _config.redoStatus === false) {
-      var jsonData = canvas_sprite.toJSON()
-      var canvasAsJson = JSON.stringify(jsonData)
-      if (_config.currentStateIndex < _config.canvasState.length - 1) {
-        var indexToBeInserted = _config.currentStateIndex + 1
-        _config.canvasState[indexToBeInserted] = canvasAsJson
-        var numberOfElementsToRetain = indexToBeInserted + 1
-        _config.canvasState = _config.canvasState.splice(0, numberOfElementsToRetain)
-      } else {
-        _config.canvasState.push(canvasAsJson)
-      }
-      _config.currentStateIndex = _config.canvasState.length - 1
-      if (_config.currentStateIndex === _config.canvasState.length - 1 && _config.currentStateIndex !== -1) {
-        this.setState({
-          redoButtonStatus: 'disabled'
-        })
-      }
-    }
-  }
-
-  save = () => {
-  }
-
-  render () {
-    const canvasOption = this.currentOptionArr[0].css
-    return (
-      <React.Fragment>
-        <div className={styles.header}><span>模版:{this.state.name}</span><span>保存于00:00:00 <Button>预览</Button><Button type="primary" onClick={this.save}>保存</Button></span></div>
-        <div className={styles.content}>
-          <div className={styles.left}>
-            <div className={styles.box}>
-              <h2>背景</h2>
-              <Form layout="vertical">
-                <Form.Item label="宽度" >
-                  <Input defaultValue={canvasOption.width} onChange={(e) => {
-                    this.currentOptionArr[0].css.width = e.target.value
-                    this.canvas_sprite.setWidth(e.target.value)
-                  }}/>
-                </Form.Item>
-                <Form.Item label="高度">
-                  <Input defaultValue={canvasOption.height} onChange={(e) => {
-                    this.currentOptionArr[0].css.height = e.target.value
-                    this.canvas_sprite.setHeight(e.target.value)
-                  }}/>
-                </Form.Item>
-                <Form.Item label="背景色">
-                  <Input defaultValue={canvasOption.background} onChange={(e) => {
-                    this.currentOptionArr[0].css.background = e.target.value
-                    this.canvas_sprite.setBackgroundColor(e.target.value)
-                    this.canvas_sprite.renderAll()
-                  }}/>
-                </Form.Item>
-              </Form>
-            </div>
-            <div className={styles.box}>
-              <h2>元素</h2>
-              <ul className={styles.elem}>
-                <li onClick={() => this.addShape(1)}>文字</li>
-                <li onClick={() => this.addShape(2)}>图片</li>
-                <li onClick={() => this.addShape(3)}>二维码</li>
-              </ul>
-            </div>
-            <div className={styles.box}>
-              <h2>图层列表</h2>
-              <ul className={styles.layer}>
-                <li className={styles.selected}>xxxx</li>
-                <li>xxxx</li>
-              </ul>
-            </div>
-          </div>
-          <div className={styles.middle}>
-            <canvas id="canvas"></canvas>
-          </div>
-          <div className={styles.right}>
-            {
-              this.activeObject.mytype === 'textGroup' && (
-                <div className={styles.box}>
-                  <h2>文字</h2>
-                  <Form layout="vertical">
-                    <Form.Item label="内容">
-                      <Input.TextArea rows={3} defaultValue={this.state.currentOptionArr[1].css.text} onChange={(e) => this.handleChange(1, 'text', e.target.value)}/>
-                    </Form.Item>
-                    <div style={{ display: 'flex' }}>
-                      <Form.Item label="字体" style={{ width: 150 }}>
-                        <Select defaultValue={this.state.currentOptionArr[1].css.fontFamily} style={{ width: 145 }} onChange={(value) => this.handleChange(1, 'fontFamily', value)}>
-                          <Option value="msyh">msyh</Option>
-                          <Option value="msyhbd">msyhbd</Option>
-                          <Option value="NotoSerifCJK">NotoSerifCJK</Option>
-                          <Option value="PingFang">PingFang</Option>
-                          <Option value="pingfang-sc-semibold">pingfang-sc-semibold</Option>
-                          <Option value="pingfang_jiancu">pingfang_jiancu</Option>
-                          <Option value="pingfang_jianxi">pingfang_jianxi</Option>
-                          <Option value="pingfang_regular">pingfang_regular</Option>
-                          <Option value="PingFang_SC">PingFang_SC</Option>
-                          <Option value="pingfang_sc_medium">pingfang_sc_medium</Option>
-                          <Option value="pingfang_sc_semibold">pingfang_sc_semibold</Option>
-                          <Option value="simhei">simhei</Option>
-                          <Option value="SourceHanSansCN-Normal">SourceHanSansCN-Normal</Option>
-                          <Option value="喜鹊招牌体">喜鹊招牌体</Option>
-                          <Option value="喜鹊聚珍体regular">喜鹊聚珍体regular</Option>
-                          <Option value="思源宋体">思源宋体</Option>
-                          <Option value="方正正粗黑简体">方正正粗黑简体</Option>
-                        </Select>
-                      </Form.Item>
-                      <Form.Item label="字体大小" style={{ flex: 1 }}>
-                        <InputNumber style={{ width: 80 }} min={12} max={100} defaultValue={this.state.currentOptionArr[1].css.fontSize} onChange={(value) => this.handleChange(1, 'fontSize', value)} />
-                      </Form.Item>
-                      <Form.Item label="字体颜色" style={{ flex: 1 }}>
-                        <RcColorPicker
-                          color={this.state.currentOptionArr[1].css.color}
-                          onChange={({ color }) => this.handleChange(1, 'color', color)}
-                        >
-                          <Input style={{ width: 80 }} />
-                        </RcColorPicker>
-                      </Form.Item>
-                    </div>
-                    <div style={{ display: 'flex' }}>
-                      <Form.Item label="X偏移" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[1].css.left} onChange={(e) => this.handleChange(1, 'left', e.target.value)}/>
-                      </Form.Item>
-                      <Form.Item label="Y偏移" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[1].css.top} onChange={(e) => this.handleChange(1, 'top', e.target.value)}/>
-                      </Form.Item>
-                    </div>
-                    <Form.Item label="文字宽度">
-                      <Input defaultValue={this.state.currentOptionArr[1].css.width} onChange={(e) => this.handleChange(1, 'width', e.target.value)}/>
-                    </Form.Item>
-                    <Form.Item label="文字行数">
-                      <Input defaultValue={this.state.currentOptionArr[1].css.maxLines} onChange={(e) => this.handleChange(1, 'maxLines', e.target.value)}/>
-                    </Form.Item>
-                    <Form.Item label="X居中">
-                      <Select defaultValue={this.state.currentOptionArr[1].css.textAlign} style={{ width: 150 }} onChange={(value) => this.handleChange(1, 'textAlign', value)}>
-                        <Option value="left">left</Option>
-                        <Option value="center">center</Option>
-                        <Option value="right">right</Option>
-                      </Select>
-                    </Form.Item>
-                    <Form.Item label="文字垂直">
-                      <Input/>
-                    </Form.Item>
-                    <Form.Item label="不透明">
-                      <Slider
-                        min={1}
-                        max={20}
-                        onChange={(value) => console.log(value)}
-                        value={10}
-                      />
-                    </Form.Item>
-                  </Form>
-                </div>
-              )
-            }
-
-            {
-              this.activeObject.mytype === 'qrcode' && (
-                <div className={styles.box}>
-                  <h2>二维码</h2>
-                  <Form layout="vertical">
-                    <Form.Item label="内容">
-                      <Input defaultValue={this.state.currentOptionArr[3].css.url} onChange={(e) => this.handleChange(3, 'url', e.target.value)}/>
-                    </Form.Item>
-                    <div style={{ display: 'flex' }}>
-                      <Form.Item label="X偏移" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[3].css.left} onChange={(e) => this.handleChange(3, 'left', e.target.value)}/>
-                      </Form.Item>
-                      <Form.Item label="Y偏移" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[3].css.top} onChange={(e) => this.handleChange(3, 'top', e.target.value)}/>
-                      </Form.Item>
-                    </div>
-                    <Form.Item label="LOGO">
-                      <Switch defaultChecked onChange={(checked) => console.log(checked)} checkedChildren="开" unCheckedChildren="关" />
-                      <Select defaultValue="wpt" style={{ width: 120, marginLeft: '5px' }}>
-                        <Option value="wpt">微拍堂</Option>
-                        <Option value="youjiang"></Option>
-                      </Select>
-                    </Form.Item>
-                    <Form.Item label="大小">
-                      <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[3].css.width} onChange={(e) => this.handleChange(3, 'width', e.target.value)}/>
-                    </Form.Item>
-                  </Form>
-                </div>
-              )
-            }
-
-            {
-              this.activeObject.mytype === 'image' && (
-                <div className={styles.box}>
-                  <h2>图片</h2>
-                  <Form layout="vertical">
-                    <Form.Item label="路径">
-                      <Input/>
-                    </Form.Item>
-                    <div style={{ display: 'flex' }}>
-                      <Form.Item label="宽" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.width} onChange={(e) => this.handleChange(2, 'width', e.target.value)}/>
-                      </Form.Item>
-                      <Form.Item label="高" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.height} onChange={(e) => this.handleChange(2, 'height', e.target.value)}/>
-                      </Form.Item>
-                    </div>
-                    <div style={{ display: 'flex' }}>
-                      <Form.Item label="X偏移" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.left} onChange={(e) => this.handleChange(2, 'left', e.target.value)}/>
-                      </Form.Item>
-                      <Form.Item label="Y偏移" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.top} onChange={(e) => this.handleChange(2, 'top', e.target.value)}/>
-                      </Form.Item>
-                    </div>
-                    <div style={{ display: 'flex' }}>
-                      <Form.Item label="圆形" style={{ flex: 1 }}>
-                        <Switch defaultChecked onChange={(checked) => {
-                          if (checked) {
-                            this.handleChange(2, 'borderRadius', 1000)
-                          } else {
-                            this.handleChange(2, 'borderRadius', 0)
-                          }
-                        }} checkedChildren="开" unCheckedChildren="关" />
-                      </Form.Item>
-                      <Form.Item label="圆角" style={{ flex: 1 }}>
-                        <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.borderRadius} onChange={(e) => this.handleChange(2, 'borderRadius', e.target.value)}/>
-                      </Form.Item>
-                    </div>
-                    <Form.Item label="高斯模糊">
-                      <Input/>
-                    </Form.Item>
-                    <Form.Item label="图片裁剪选项">
-                      <Input/>
-                    </Form.Item>
-                  </Form>
-                </div>
-              )
-            }
-          </div>
-        </div>
-      </React.Fragment>
-    )
-  }
-}

+ 1 - 1
src/pages/poster/edit/index.less

@@ -142,7 +142,7 @@
   background: #dcdcdc;
   margin: 0px 10px;
   // min-height: 100vh;
-  min-width: 750px;
+  // min-width: 750px;
   overflow: auto;
 }
 .right{

+ 40 - 1
yarn.lock

@@ -7033,6 +7033,11 @@ fast-levenshtein@~2.0.4:
   resolved "http://npm.wpt.la/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
+fast-memoize@^2.5.1:
+  version "2.5.2"
+  resolved "http://npm.wpt.la/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e"
+  integrity sha1-eeO7ak7IZ+pAug5xRoFvbNzptX4=
+
 fastclick@1.0.6:
   version "1.0.6"
   resolved "http://npm.wpt.la/fastclick/-/fastclick-1.0.6.tgz#161625b27b1a5806405936bda9a2c1926d06be6a"
@@ -10301,6 +10306,11 @@ lodash.camelcase@^4.1.1, lodash.camelcase@^4.3.0:
   resolved "http://npm.wpt.la/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
   integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
 
+lodash.clonedeep@^4.5.0:
+  version "4.5.0"
+  resolved "http://npm.wpt.la/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+  integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
+
 lodash.debounce@^4.0.0, lodash.debounce@^4.0.8:
   version "4.0.8"
   resolved "http://npm.wpt.la/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@@ -10390,7 +10400,7 @@ lodash.templatesettings@^4.0.0:
   dependencies:
     lodash._reinterpolate "^3.0.0"
 
-lodash.throttle@^4.0.0:
+lodash.throttle@^4.0.0, lodash.throttle@^4.1.1:
   version "4.1.1"
   resolved "http://npm.wpt.la/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
   integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
@@ -13935,6 +13945,13 @@ rc@^1.2.7, rc@^1.2.8:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
+re-resizable@6.3.2:
+  version "6.3.2"
+  resolved "http://npm.wpt.la/re-resizable/-/re-resizable-6.3.2.tgz#27cc984af6ea5dbafd2b79f64c5224a6e1722fbe"
+  integrity sha1-J8yYSvbqXbr9K3n2TFIkpuFyL74=
+  dependencies:
+    fast-memoize "^2.5.1"
+
 react-ace@^8.1.0:
   version "8.1.0"
   resolved "http://npm.wpt.la/react-ace/-/react-ace-8.1.0.tgz#6680accb7306a1850e6ef4370a00f0007eadc8b4"
@@ -14085,6 +14102,14 @@ react-dom@^16.8.6, react-dom@^16.9.0:
     prop-types "^15.6.2"
     scheduler "^0.16.2"
 
+react-draggable@4.3.1:
+  version "4.3.1"
+  resolved "http://npm.wpt.la/react-draggable/-/react-draggable-4.3.1.tgz#f9c0cdcf2279ec5b79c65b70cdfd9361d82fa9ee"
+  integrity sha1-+cDNzyJ57Ft5xltwzf2TYdgvqe4=
+  dependencies:
+    classnames "^2.2.5"
+    prop-types "^15.6.0"
+
 react-error-overlay@5.1.6, react-error-overlay@^5.1.6:
   version "5.1.6"
   resolved "http://npm.wpt.la/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d"
@@ -14187,6 +14212,15 @@ react-redux@^7.1.0:
     prop-types "^15.7.2"
     react-is "^16.9.0"
 
+react-rnd@^10.1.8:
+  version "10.1.9"
+  resolved "http://npm.wpt.la/react-rnd/-/react-rnd-10.1.9.tgz#7413f0de084ef206f0e2ba4db601592f5e05f752"
+  integrity sha1-dBPw3ghO8gbw4rpNtgFZL14F91I=
+  dependencies:
+    re-resizable "6.3.2"
+    react-draggable "4.3.1"
+    tslib "1.11.1"
+
 react-router-config@1.0.0-beta.4:
   version "1.0.0-beta.4"
   resolved "http://npm.wpt.la/react-router-config/-/react-router-config-1.0.0-beta.4.tgz#d202496dd0eabdf06cf24eb0793031f6891eef01"
@@ -16925,6 +16959,11 @@ ts-pnp@^1.1.2:
   resolved "http://npm.wpt.la/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90"
   integrity sha1-ricSaWDrrvuHTG1/pHKXKasgDZA=
 
+tslib@1.11.1:
+  version "1.11.1"
+  resolved "http://npm.wpt.la/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
+  integrity sha1-6xXRKIJ/vuKEFUnhcfRe0zisfjU=
+
 tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
   version "1.10.0"
   resolved "http://npm.wpt.la/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"