import Constants from '../configuration/Constants'
import { recognizerLogger as logger } from '../configuration/LoggerConfig'
import * as InkModel from '../model/InkModel'
import * as SmartGuide from '../smartguide/SmartGuide'
import * as RecognizerContext from '../model/RecognizerContext'
import { launchExport } from '../Editor'
/**
* Emit events
* @param {Editor} editor
* @param {Object} data
* @param {...String} types
* @return {Model}
*/
export function emitEvents (editor, data, ...types) {
const editorRef = editor
types.forEach((type) => {
switch (type) {
case Constants.EventType.RENDERED:
break // Internal use only
case Constants.EventType.UNDO:
case Constants.EventType.REDO:
case Constants.EventType.CLEAR:
case Constants.EventType.CONVERT:
case Constants.EventType.EXPORT:
editor.emit.call(editor.domElement, type)
break
case Constants.EventType.LOADED:
case Constants.EventType.CHANGED:
editor.emit.call(editor.domElement, type, {
initialized: editor.initialized,
canUndo: editor.canUndo,
canRedo: editor.canRedo,
canClear: editor.canClear,
isEmpty: editor.isEmpty,
possibleUndoCount: editor.possibleUndoCount,
undoStackIndex: editor.undoStackIndex,
canConvert: editor.canConvert,
canExport: editor.canExport
})
break
case Constants.EventType.EXPORTED:
window.clearTimeout(editorRef.notifyTimer)
editorRef.notifyTimer = window.setTimeout(() => {
editor.emit.call(editor.domElement, type, {
exports: editor.exports
})
}, editorRef.configuration.processDelay)
break
case Constants.EventType.SUPPORTED_IMPORT_MIMETYPES:
editor.emit.call(editor.domElement, type, {
mimeTypes: editor.supportedImportMimeTypes
})
break
case Constants.EventType.ERROR:
editor.emit.call(editor.domElement, type, data)
break
case Constants.EventType.IDLE:
editor.emit.call(editor.domElement, type, {
idle: editor.idle
})
break
default:
logger.debug(`No valid trigger configured for ${type}`)
break
}
})
}
/**
* Manage recognized model
* @param {Editor} editor
* @param {Model} model
* @param {...String} types
*/
export function manageRecognizedModel (editor, model, ...types) {
const editorRef = editor
const modelRef = model
logger.debug(`model changed callback on ${types} event(s)`, model)
if (modelRef.creationTime === editor.model.creationTime) {
// Merge recognized model if relevant and return current editor model
if ((modelRef.rawStrokes.length === editor.model.rawStrokes.length) &&
(modelRef.lastPositions.lastSentPosition >= editor.model.lastPositions.lastReceivedPosition)) {
editorRef.model = InkModel.mergeModels(editorRef.model, modelRef)
if (InkModel.needRedraw(editorRef.model) || types.includes(Constants.EventType.RENDERED)) {
editor.renderer.drawModel(editor.rendererContext, editorRef.model, editor.stroker)
}
} else {
editorRef.model = modelRef
editor.renderer.drawModel(editor.rendererContext, editorRef.model, editor.stroker)
}
emitEvents(editor, undefined, ...types)
}
if (editor.configuration.recognitionParams.type === 'TEXT' &&
editor.configuration.recognitionParams.protocol !== 'REST' &&
editor.configuration.recognitionParams.iink.text.mimeTypes.includes(Constants.Exports.JIIX) &&
editor.configuration.recognitionParams.iink.text.smartGuide) {
// eslint-disable-next-line no-use-before-define
editorRef.smartGuide = SmartGuide.launchSmartGuide(editor.smartGuide, modelRef.exports)
}
if ((InkModel.extractPendingStrokes(model).length > 0) &&
(!editor.recognizer.addStrokes) && // FIXME: Ugly hack to avoid double export (addStrokes + export)
(editor.configuration.triggers.exportContent !== Constants.Trigger.DEMAND)) {
launchExport(editor, model)
}
}
/**
* Method called when server respond with an error
* Use in catch on Promises
* @param {Editor} editor
* @param {Object} err
* @param {...String} events
*/
export function handleError (editor, err, ...events) {
const editorRef = editor
if (err.type !== 'close') {
// Handle any error from all above steps
logger.error('Error while firing the recognition', err)
}
let errorCode = err.code
let message = err.message
if (err.type === 'message') {
if (err.data.code) {
errorCode = err.data.code
}
if (err.data.message) {
message = err.data.message
}
}
editor.recognizerContext.error = err
switch (errorCode) {
case 'no.activity':
message = Constants.Error.NO_ACTIVITY
break
case 'api.invalid.format':
message = err.message
break
case 'access.not.granted':
message = Constants.Error.WRONG_CREDENTIALS
break
case 'session.too.old':
message = Constants.Error.TOO_OLD
break
case 1000:
case 1006:
if (editorRef.error.style.display === 'none') {
message = Constants.Error.NOT_REACHABLE
}
break
default:
if (!message) {
message = Constants.Error.CANT_ESTABLISH
}
break
}
editorRef.error.innerText = message
if (
(editorRef.error.innerText === Constants.Error.TOO_OLD || err.reason === 'CLOSE_RECOGNIZER') &&
RecognizerContext.canReconnect(editor.recognizerContext)
) {
logger.info('Reconnection is available', err)
editorRef.error.style.display = 'none'
} else {
editorRef.loader.style.display = 'none'
editorRef.error.style.display = 'initial'
emitEvents(editor, err, Constants.EventType.ERROR, ...events)
}
}
/**
* Method called when server respond correctly to request or WS
* Use in then on Promises
* @param {Editor} editor
* @param {Object} model
* @param {...String} events
*/
export function handleSuccess (editor, model, ...events) {
const editorRef = editor
if (editor.undoRedoManager.updateModel) {
editor.undoRedoManager.updateModel(editor.undoRedoContext, model)
.then(({ res, types }) => {
manageRecognizedModel(editorRef, res, ...[...events, ...types].filter((el, i, a) => i === a.indexOf(el)))
})
} else {
if (editorRef.error.style.display === 'initial') {
editorRef.error.style.display = 'none'
}
manageRecognizedModel(editorRef, model, ...events)
}
}