recognizer/rest/iinkRestRecognizer.js

/* eslint-disable no-underscore-dangle */
import * as NetworkInterface from './networkInterface'
import * as RecognizerContext from '../../model/RecognizerContext'
import { recognizerLogger as logger } from '../../configuration/LoggerConfig'
import Constants from '../../configuration/Constants'
import * as InkModel from '../../model/InkModel'
import * as StrokeComponent from '../../model/StrokeComponent'
import * as DefaultTheme from '../../configuration/DefaultTheme'
import * as DefaultPenStyle from '../../configuration/DefaultPenStyle'
import { handleError, handleSuccess } from '../RecognizerService'

export { init, close, clear, reset } from '../DefaultRecognizer'

/**
 * Recognizer configuration
 * @type {RecognizerInfo}
 */
export const iinkRestConfiguration = {
  types: [Constants.RecognitionType.TEXT, Constants.RecognitionType.DIAGRAM, Constants.RecognitionType.MATH, Constants.RecognitionType.RAWCONTENT],
  protocol: Constants.Protocol.REST,
  availableTriggers: {
    exportContent: [
      Constants.Trigger.QUIET_PERIOD,
      Constants.Trigger.DEMAND
    ]
  }
}

/**
 * Get the configuration supported by this recognizer
 * @return {RecognizerInfo}
 */
export function getInfo () {
  return iinkRestConfiguration
}

/**
 * @param {String} suffixUrl
 * @param {RecognizerContext} recognizerContext
 * @param {Model} model
 * @param {function} buildMessage
 * @param {String} conversionState
 * @param {String} mimeType
 * @return {Promise.<Model>} Promise that return an updated model as a result
 */
export function postMessage (suffixUrl, recognizerContext, model, buildMessage, conversionState = '', mimeType) {
  const configuration = recognizerContext.editor.configuration
  return NetworkInterface.post(recognizerContext, `${configuration.recognitionParams.server.scheme}://${configuration.recognitionParams.server.host}${suffixUrl}`, buildMessage(recognizerContext, model, conversionState), mimeType)
    .then((response) => {
      logger.debug('iinkRestRecognizer success', response)
      const positions = recognizerContext.lastPositions
      positions.lastReceivedPosition = positions.lastSentPosition
      const recognizerContextReference = RecognizerContext.updateRecognitionPositions(recognizerContext, positions)
      if (response.instanceId) {
        recognizerContextReference.instanceId = response.instanceId
      }
      return response
    })
}

function buildTextConf (configuration) {
  return {
    text: configuration.recognitionParams.iink.text,
    lang: configuration.recognitionParams.iink.lang,
    export: configuration.recognitionParams.iink.export
  }
}

function buildMathConf (configuration) {
  return {
    math: configuration.recognitionParams.iink.math,
    lang: configuration.recognitionParams.iink.lang,
    export: configuration.recognitionParams.iink.export
  }
}

function buildDiagramConf (configuration) {
  return {
    diagram: configuration.recognitionParams.iink.diagram,
    lang: configuration.recognitionParams.iink.lang,
    export: configuration.recognitionParams.iink.export
  }
}

function buildRawContentConf (configuration) {
  return {
    'raw-content': {
      recognition: configuration.recognitionParams.iink['raw-content'].recognition
    },
    lang: configuration.recognitionParams.iink.lang,
    export: configuration.recognitionParams.iink.export
  }
}

function buildData (recognizerContext, model, conversionState) {
  const configuration = recognizerContext.editor.configuration
  let dataConf

  if (configuration.recognitionParams.type === 'TEXT') {
    dataConf = buildTextConf(configuration)
  } else if (configuration.recognitionParams.type === 'MATH') {
    dataConf = buildMathConf(configuration)
  } else if (configuration.recognitionParams.type === 'DIAGRAM') {
    dataConf = buildDiagramConf(configuration)
  } else if (configuration.recognitionParams.type === 'Raw Content') {
    dataConf = buildRawContentConf(configuration)
  }

  const newStrokes = []
  model.strokeGroups.forEach((group) => {
    const newPenStyle = JSON.stringify(group.penStyle) === '{}' ? null : DefaultPenStyle.toCSS(group.penStyle)
    const newGroup = {
      penStyle: newPenStyle,
      strokes: group.strokes.map(stroke => StrokeComponent.toJSON(stroke))
    }
    newStrokes.push(newGroup)
  })

  const contentType = configuration.recognitionParams.type === 'Raw Content' ? 'Raw Content' : configuration.recognitionParams.type.charAt(0)
    .toUpperCase() + configuration.recognitionParams.type.slice(1)
    .toLowerCase()

  const data = {
    configuration: dataConf,
    xDPI: 96,
    yDPI: 96,
    contentType,
    theme: DefaultTheme.toCSS(recognizerContext.editor.theme),
    strokeGroups: newStrokes
  }

  if (recognizerContext.editor.domElement) {
    data.height = recognizerContext.editor.domElement.clientHeight
    data.width = recognizerContext.editor.domElement.clientWidth
  }

  if (conversionState) {
    data.conversionState = 'DIGITAL_EDIT'
  }

  InkModel.updateModelSentPosition(model)
  return data
}

function extractExports (configuration, mimeType, res) {
  const exports = {}
  exports[mimeType] = res
  return exports
}

function resultCallback (recognizerContext, model, configuration, res, mimeType) {
  logger.debug('iinkRestRecognizer result callback', model)
  const modelReference = InkModel.updateModelReceivedPosition(model)
  modelReference.rawResults.exports = res
  if (modelReference.exports) {
    Object.assign(modelReference.exports, extractExports(configuration, mimeType, res))
  } else {
    modelReference.exports = extractExports(configuration, mimeType, res)
  }
  logger.debug('iinkRestRecognizer model updated', modelReference)

  if (recognizerContext.editor.undoRedoManager) {
    handleSuccess(recognizerContext.editor, modelReference, Constants.EventType.EXPORTED, Constants.EventType.IDLE)
  }
}

function callPostMessage (recognizerContext, model, mimeType) {
  const configuration = recognizerContext.editor.configuration
  return postMessage('/api/v4.0/iink/batch', recognizerContext, model, buildData, configuration.restConversionState, mimeType)
    .then((res) => {
      resultCallback(recognizerContext, model, configuration, res, mimeType)
      return model
    })
    .catch((err) => {
      handleError(recognizerContext.editor, err)
      return err
    })
}

/**
 * Export content
 * @param {RecognizerContext} recognizerContext Current recognizer context
 * @param {Model} model Current model
 * @param {Array} requestedMimeTypes
 */
export function export_ (recognizerContext, model, requestedMimeTypes) {
  const configuration = recognizerContext.editor.configuration
  if (requestedMimeTypes) {
    return Promise.all(requestedMimeTypes.map(mimeType => callPostMessage(recognizerContext, model, mimeType)))
  } else if (configuration.recognitionParams.type === 'TEXT') {
    return Promise.all(configuration.recognitionParams.iink.text.mimeTypes.map(mimeType => callPostMessage(recognizerContext, model, mimeType)))
  } else if (configuration.recognitionParams.type === 'DIAGRAM') {
    return Promise.all(configuration.recognitionParams.iink.diagram.mimeTypes.map(mimeType => callPostMessage(recognizerContext, model, mimeType)))
  } else if (configuration.recognitionParams.type === 'MATH') {
    return Promise.all(configuration.recognitionParams.iink.math.mimeTypes.map(mimeType => callPostMessage(recognizerContext, model, mimeType)))
  } else if (configuration.recognitionParams.type === 'Raw Content') {
    return Promise.all(configuration.recognitionParams.iink['raw-content'].mimeTypes.map(mimeType => callPostMessage(recognizerContext, model, mimeType)))
  }
  return Promise.reject(new Error('Export failed'))
}

/**
 * Ask for conversion using DIGITAL_EDIT
 * @param {RecognizerContext} recognizerContext Current recognizer context
 * @param {Model} model Current model
 */
export function convert (recognizerContext, model) {
  const configuration = recognizerContext.editor.configuration
  postMessage('/api/v4.0/iink/batch', recognizerContext, model, buildData, 'DIGITAL_EDIT')
    .then(res => resultCallback(model, configuration, res))
    .catch(err => handleError(recognizerContext.editor, err))
}

/**
 * Resize
 * @param {RecognizerContext} recognizerContext Current recognition context
 * @param {Model} model Current model
 */
export function resize (recognizerContext, model) {
  if (model.strokeGroups.length) {
    export_(recognizerContext, model)
  }
}