markupJs

2022-2-18
import React, { useRef, useImperativeHandle, Fragment, forwardRef, memo } from 'react';
import { message } from 'antd';
import { nanoid } from 'nanoid/async';
import PropTypes from 'prop-types';
// import html2canvas from 'html2canvas';
import domtoimage from 'dom-to-image';
import { base64ToBlob } from '@/utils';

const getEditModel = MarkupCore => ({
  arrow: MarkupCore.EditModeArrow,
  circle: MarkupCore.EditModeCircle,
  lace: MarkupCore.EditModeCloud,
  line: MarkupCore.EditModeFreehand,
  rect: MarkupCore.EditModeRectangle,
  txt: MarkupCore.EditModeText,
});

// 获取当前viewerState(定位模型整体数据)
const getViewerState = async viewer => {
  let tmpState = await viewer.getState();
  tmpState = JSON.stringify(tmpState);
  tmpState = tmpState.replace(/\"/g, "'"); // eslint-disable-line
  return tmpState;
};

// 获取当前viewer截图
// const getViewerScreenShot = async () => await viewer.getScreenShot());
const getViewerScreenShot = viewer => {
  return new Promise(resolve => {
    viewer.getScreenShot(viewer.container.clientWidth, viewer.container.clientHeight, function (
      base64
    ) {
      resolve(base64);
    });
  });
};

/**
 * 解析模型定位数据处理
 * @param {String} location
 */
const resolveModelLocation = location => JSON.parse(location.replace(/'/g, '"'));

/**
 * 解析涂鸦数据
 * @param {String} markup
 */
const resolveModelMarkup = markup => markup.replace(/(^\s*)|(\s*$)/g, '');

const Markup = forwardRef((props, ref) => {
  const { viewer, AV, modelPath } = props;
  const markupExt = useRef(null); // markup extension实例

  const handleEnterEditMode = () => {
    if (markupExt.current) {
      markupExt.current.show();
      markupExt.current.enterEditMode();
    }
  };

  const handleChangeEditMode = mode => {
    if (!markupExt.current) return message.warning('涂鸦异常');

    const MarkupCore = AV.Extensions.Markups.Core;
    const EditeMode = getEditModel(MarkupCore)[mode];

    const resultModel = new EditeMode(markupExt.current);
    resultModel && markupExt.current?.changeEditMode(resultModel);
  };

  // 获取含 Markups 的截图
  const getContainMarkUpBlob = () => {
    return new Promise((resolve, reject) => {
      try {
        // const screenshot = new Image();
        // const canvas = document.createElement('canvas');
        // screenshot.src = imgPath;

        // screenshot.onload = () => {
        //  // 如何在 Forge Viewer 里获取含 Markups 的截图
        //  // https://segmentfault.com/a/1190000012739224
        //  canvas.width = viewer?.container?.clientWidth;
        //  canvas.height = viewer?.container?.clientHeight;

        //  const ctx = canvas.getContext('2d');
        //  ctx.clearRect(0, 0, canvas.width, canvas.height);
        //  ctx.drawImage(screenshot, 0, 0, canvas.width, canvas.height);

        //  // markupExt.current?.loadMarkups(markupSVG, 'layerName');

        //  try {
        //    markupExt.current?.renderToCanvas(ctx, () => {
        //      canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.8);
        //    });
        //  } catch (e) {
        //    console.log(e);
        //    // reject(e);
        //    canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.8);
        //  }
        //  // resolve(base64);
        // };

        // screenshot.onerror = () => reject('error');

        const modelWrap = viewer?.canvasWrap?.parentNode;

        if (!modelWrap) reject('error');

        // // 使用html2canvas获取markup截图(无法获取涂鸦中的文字)
        // // http://html2canvas.hertzen.com/configuration
        // const canvasWrapBouding = viewer?.canvasWrap?.getBoundingClientRect();
        // html2canvas(modelWrap, {
        //  // backgroundColor: null,
        //  foreignObjectRendering: true,
        //  width: viewer?.canvas?.width,
        //  height: viewer?.canvas?.height,
        //  scrollX: canvasWrapBouding.x,
        //  scrollY: canvasWrapBouding.y,
        //  ignoreElements: el => {
        //    if (
        //      (el.nodeName &&
        //        el.nodeName.toLowerCase() === 'div' &&
        //        el.parentNode &&
        //        el.parentNode === modelWrap &&
        //        el.classList &&
        //        !el.classList.contains('canvas-wrap')) ||
        //      (el.nodeName &&
        //        el.nodeName.toLowerCase() === 'div' &&
        //        el.classList.contains('modelName'))
        //    ) {
        //      return true;
        //    }

        //    return false;
        //  },
        // }).then(function (canvasNode) {
        //  canvasNode.toBlob(blob => resolve(blob), 'image/jpeg', 0.8);
        // });

        // 使用dom-to-image获取markup截图
        // https://www.npmjs.com/package/dom-to-image
        domtoimage
          .toBlob(modelWrap, {
            quality: 0.8,
            filter: node =>
              !(
                (node.nodeName.toLowerCase() === 'div' &&
                  !node.classList.contains('canvas-wrap')) ||
                (node.nodeName.toLowerCase() === 'svg' && !node.hasAttribute('layer-order-id'))
              ),
          })
          .then(blob => {
            resolve(blob);
          })
          .catch(error => {
            console.log(error);
            reject('error');
          });
      } catch (error) {
        reject('error');
      }
    });
  };

  // 获取当前涂鸦数据
  const getMarkState = async () => {
    const randomId = await nanoid();
    let markStr = await (markupExt.current?.duringEditMode && markupExt.current?.generateData());
    markStr = markStr && markStr.replace(/\"/g, "'"); // eslint-disable-line
    // markStr = markStr && markStr.replace(/(^\s*)|(\s*$)/g, '');
    markStr = markStr && markStr.length > 0 && `${randomId}@BIM@${markStr}`;

    return markStr;
  };

  const leaveEditMode = () => {
    if (markupExt.current) {
      markupExt.current.leaveEditMode();
      markupExt.current.hide(); // 隐藏涂丫工具,并回复导览工具
    }
  };

  /**
   * 加载上一次涂鸦数据和视点定位
   * 步骤:
   * 1. 先监听视点AV.VIEWER_STATE_RESTORED_EVENT
   * 2. 加载视点viewer.restoreState(location);
   * 3. 监听相机完成事件AV.CAMERA_TRANSITION_COMPLETED
   * 4. 相机完成后,执行markupExt.loadMarkups(markup)加载涂鸦数据
   * @param {String} location 视点数据
   * @param {String} markup   涂鸦数据
   * @param {Number} pdfPageNum pdf模型页码
   */
  const restoreMarkupState = ({ location, markup, pdfPageNum }) => {
    if (!markupExt.current || !viewer) return message.warning('涂鸦加载异常');
    const locationState = location && resolveModelLocation(location);
    const markupState = markup && resolveModelMarkup(markup);
    const markupStateArr = markupState && markupState.split('@#@');
    const isPdf = viewer?.model?.isPdf() || false;

    // 监听相机事件
    const handleCameraTransitionCompleted = () => {
      markupExt.current?.enterEditMode(); // 进入涂鸦模式
      markupStateArr &&
        Array.isArray(markupStateArr) &&
        markupStateArr.length &&
        markupStateArr.forEach(state => {
          const currentMarkupState = state.split('@BIM@');
          // 恢复涂鸦数据
          markupExt.current?.loadMarkups(
            currentMarkupState[1].replace(/'/g, '"'),
            currentMarkupState[0]
          );
        });

      // 每次加载涂鸦后,模式都改为自由线
      const currentMarkupEditMode = markupExt.current?.editMode;
      currentMarkupEditMode?.type !== 'line' && handleChangeEditMode('line');

      viewer.removeEventListener(AV.CAMERA_TRANSITION_COMPLETED, handleCameraTransitionCompleted);
    };

    // 监听restore事件完成
    const handleViewerStateRestored = () => {
      viewer.addEventListener(AV.CAMERA_TRANSITION_COMPLETED, handleCameraTransitionCompleted);

      viewer.removeEventListener(AV.VIEWER_STATE_RESTORED_EVENT, handleViewerStateRestored);
    };

    const handler = function () {
      viewer.addEventListener(AV.VIEWER_STATE_RESTORED_EVENT, handleViewerStateRestored);

      viewer.impl.setCutPlanes(); // 禁止所有切面(x,y,z),解除切面状态
      markupExt.current?.unloadMarkupsAllLayers(); // 清除上一次的涂鸦数据
      leaveEditMode(); // 相当于首次进入。模型可拖动等操作
      viewer.setLayerVisible(null, true); // 如果是图纸,需要执行
      viewer.restoreState(locationState); // 恢复视点定位数据
    };

    if (isPdf) {
      viewer?.loadExtension('Autodesk.PDF').then(() => {
        // pdf加载默认设置为第一页
        viewer?.loadModel(modelPath, { page: pdfPageNum || 1 }, () => {
          handler();
        });
        viewer?.setSwapBlackAndWhite(false); //false使图纸背景变为白色,true使图纸背景变为黑色
      });
    } else {
      handler();
    }
  };

  useImperativeHandle(ref, () => ({
    // 获取markup extension实例
    getMarkupExt: () => markupExt.current,
    // 进入涂鸦模式
    enterEditMode: () => handleEnterEditMode(),
    // 退出涂鸦模式
    leaveEditMode: () => leaveEditMode(),
    // 修改涂鸦模式
    changeEditMode: mode => handleChangeEditMode(mode),
    // 上一步
    handlePrevStep: () => markupExt.current?.rePreviousMarkupStack(),
    // 下一步
    handleNextStep: () => markupExt.current?.reNextMarkupStack(),
    // 选择清除
    handleDeleteOne: () => markupExt.current?.handleDeleteOne(),
    // 全部清除
    handleDeleteAll: () => markupExt.current?.handleDeleteAll(),
    // 修改颜色
    handleChangeColor: color => markupExt.current?.changeMarkupColor(color),
    // 修改字体大小和线条粗细
    handleChangeFontSizeAndLineWidth: num => {
      const sliderVal = Number.parseInt(num) / 100;
      markupExt.current?.changeTextFontSize(sliderVal);
      markupExt.current?.changeLineTypeWidth(sliderVal);
    },
    // 保存
    handleSave: async () => {
      if (!viewer) return message.warning('保存失败,模型对象丢失');
      let resultMarkUpBlob = null;

      const screenShotBase64 = await getViewerScreenShot(viewer); // 获取viewer截图
      // 获取包含markup的截图
      const [error, markUpBlob] = await getContainMarkUpBlob(screenShotBase64)
        .then(data => [null, data])
        .catch(error => [error, null]);

      if (screenShotBase64) {
        if (error) {
          resultMarkUpBlob = await base64ToBlob(screenShotBase64); // base64转blob对象
          // message.warning('生成涂鸦截图失败,请重试');
        } else {
          resultMarkUpBlob = markUpBlob;
        }
      }

      const screenShot = resultMarkUpBlob; // blob
      const location = await getViewerState(viewer);
      const markup = await getMarkState();

      return {
        location,
        screenShot,
        markup,
      };
    },
    // 加载涂鸦数据
    restoreMarkupState: (...args) => {
      handleEnterEditMode();
      restoreMarkupState(...args);
    },
    // 首次加载涂鸦
    loadMarkUpExtension: (oViewer, callback) => {
      const resultViewer = oViewer || viewer;

      resultViewer
        .loadExtension('Autodesk.Viewing.MarkupsCore')
        .then(ext => {
          markupExt.current = ext; // 设置markup extension实例

          if (window.top.$markupGlobalData) {
            window.top.$markupGlobalData.markupExt = ext;
            window.top.$markupGlobalData.viewer = viewer;
          }

          leaveEditMode();
          console.log('成功加载涂鸦');
          return ext;
        })
        .then(ext => {
          callback && callback(ext);
        })
        .catch(err => {
          console.log(err);
          message.warning('markup load fail');
        });
    },
    // 卸载涂鸦
    unloadMrkupExtension: () => {
      leaveEditMode();
      viewer?.unloadExtension('Autodesk.Viewing.MarkupsCore');
      markupExt.current = null;
      console.log('成功卸载涂鸦');
    },
    // 获取当前涂鸦模式
    getMarkupEditMode: () => markupExt.current?.editMode,
  }));

  return <Fragment />;
});

Markup.propTypes = {
  viewer: PropTypes.any,
  AV: PropTypes.any,
  modelPath: PropTypes.string,
};

export { getViewerScreenShot, getViewerState, resolveModelLocation, resolveModelMarkup };
export default memo(Markup);