TL; DR - мы создали веб-приложение для редактирования рукописных диаграмм на основе облачной технологии MyScript и фреймворка Vue.js.

Как вы можете написать диаграмму в браузере, предварительно просмотреть ее и использовать с любым конструктором презентаций?

Это то, что мы будем строить в этой статье, используя MyScriptJS и фреймворк Vue.js.

Проверьте веб-приложение в Интернете или найдите исходный код в нашем репозитории GitHub.

Возможности веб-приложения

Прежде чем мы начнем углубляться в детали, вот функции, которые будут интегрированы.

Как вы, возможно, видели в демонстрации, приложение позволяет использовать существующую диаграмму, чтобы показать вам некоторые возможности распознавания, или начать все заново с пустым холстом.

В режиме редактирования предлагается несколько вариантов:

MyScriptJS, интерфейсный фреймворк для распознавания рукописного текста

Распознавание обеспечивается MyScript Interactive Ink SDK, также называемым iink SDK, который представляет собой кроссплатформенный инструментарий разработки, предоставляемый MyScript.

Поскольку это будет веб-приложение, мы будем использовать библиотеку Javascript MyScriptJS, которая использует MyScript Cloud для распознавания рукописного ввода в Интернете. Вы можете найти полное руководство о том, как эта архитектура работает на веб-сайте разработчика MyScript, но вот краткое объяснение.

MyScriptJS используется для захвата штрихов из любого браузера с помощью пера, стилуса или мыши. Он также будет управлять информацией о рендеринге (используя svg или холст), когда пользователь пишет в редакторе. Используя REST или WebSockets, MyScriptJS будет управлять соединением между клиентом и сервером и отправлять захваченные штрихи, а также другие параметры (цвет, толщину, инструкции по отмене / повторению и т. Д.). Затем сервер обработает рукописный ввод и предоставит клиенту всю необходимую информацию, такую ​​как: распознанный текст, математика, формы и т. Д. В различных форматах в зависимости от выбранного типа содержимого.

В случае использования нашего веб-приложения мы используем то, что мы называем пакетным режимом (с использованием REST). Это означает, что мы отправляем штрихи не постепенно, а за один раз, когда пользователь просит об этом. Это также означает, что все функции интерактивности недоступны в пакетном режиме (но скоро будут использоваться веб-сокеты). Для полного использования функций, предлагаемых iink SDK, вы можете проверить наше приложение для заметок Nebo.

Структура и ресурсы проекта

Для создания этого веб-приложения мы решили использовать фреймворк Vue.js. В этой статье предполагается, что вы знакомы с ней или, по крайней мере, имеете представление о прогрессивном фреймворке JavaScript.

Состав

Мы начали использовать шаблон vue-webpack-markerplate, который обеспечивает полнофункциональную настройку webpack для создания приложения Vue и позволяет быстро начать работу, не задумываясь о конфигурации. Шаблон предоставляет различные функции для разработки и сборки.

Вы можете найти полную документацию о шаблоне, а также подробное объяснение структуры проекта.

Мы будем говорить здесь только о структуре исходных папок.

  • src/ - основной код
  • components/ - Компоненты Vue
  • router/ - маршруты Vue
  • utils/ - файлы JavaScript
  • event-bus.js - используется для связи между компонентами
  • App.vue - точка входа в приложение
  • main.js - точка входа в сборку

Компоненты Vue

Давайте поговорим немного о компонентах Vue. Мы разделили папку компонентов на три подпапки для трех основных частей приложения:

  • start-page/ - целевая страница, которую пользователи увидят в первую очередь (кроме мобильных устройств из-за ограничения скорости реакции).
  • edit-page/ - страница редактирования, это будет основная область, где пользователь будет иметь доступ к редактированию диаграммы
  • header/ - заголовок, который будет на обеих страницах

Ядро приложения будет находиться в папке edit-page.

Разработка проекта

Интегрируйте MyScriptJS в компонент редактора

В этой части мы сосредоточимся на том, как интегрировать MyScriptJS и связывать события, чтобы создать то, что мы называем нашим компонентом редактора.

Шаблон

<template> 
<div :style="{display: displayStyle}" class="editor"     @pointerdown="pointerDown" @loaded="loaded" @changed="changed($event)" @exported="exported($event)" touch-action="none" ref="editor"> 
</div> 
</template>

Шаблон редактора представляет собой единый блок div с четырьмя слушателями событий:

  • Pointerdown, будет срабатывать каждый раз, когда указатель становится активным
  • Загружен, будет запущен, когда редактор будет готов и полностью загружен.
  • Изменено, будет срабатывать каждый раз при изменении содержимого и содержать полезную информацию о возможностях редактора (можно очистить, можно повторить и т. д.)
  • Экспортировано, будет запускаться для каждого нового экспорта и будет содержать экспортированные данные (svg и pptx для нашего варианта использования диаграммы).

События загружено, изменено и экспортировано запускаются непосредственно из MyScriptJS.

Мы также используем директивы :ref, поскольку нам нужен элемент dom для инициализации редактора с помощью MyScriptJS.

Импортировать

import * as FileSaver from 'file-saver'; 
import MyScript from 'myscript/dist/myscript.esm'; 
import EventBus from '../../event-bus'; 
import { attach, detach } from '../../utils/custom_grabber';

Основным импортом здесь является MyScriptJS, который доступен как модуль ES6, что упрощает импорт. Мы также импортируем FileSaver для быстрого сохранения в pptx, нашу шину событий для связи между компонентами и двумя функциями, attach и detach, которые представляют собой «настраиваемый граббер для редактор".

Управляйте событиями редактора

Поскольку мы размещали слушателей событий с помощью директив Vue, мы полагались на методы, определенные в части методы компонента Vue.

Для событий pointerdown мы хотим создать событие, чтобы другие компоненты знали, что в редакторе был запущен указатель вниз. Подробнее о взаимодействии компонентов мы поговорим ниже.

pointerDown() { EventBus.$emit('pointerDown'); }

Для загруженных событий мы хотим проверить, решил ли пользователь импортировать диаграмму или начал новую. Если diagramData существует, мы импортируем его, используя метод reDraw редактора.

loaded() { 
  if (this.diagramData) {
    this.editor.reDraw(this.diagramData.rawStrokes, this.diagramData.strokeGroups);
  }
}

Для измененных событий мы будем генерировать событие changed с объектом в качестве полезной нагрузки, содержащим начальное событие, а также указание на то, может ли редактор очистить или нет (используя количество штрихов в модели редактора).

changed(event) { 
  EventBus.$emit('changed', 
    { event, canClear: this.editor.model.rawStrokes.length > 0, });
}

Экспортированные события немного сложнее. Мы по-прежнему хотим генерировать событие для других компонентов с объектом в качестве полезной нагрузки, содержащим как экспорт (здесь в формате svg и pptx), так и ширину и высоту клиента (полезно для превью svg). Вторая часть используется для сохранения экспорта в формате pptx, если требуется сохранение. Если пользователь нажимает кнопку «Сохранить» перед запросом предварительного просмотра, мы выполним экспорт перед сохранением диаграммы.

exported(event) { 
  this.exports = event.detail.exports;
  EventBus.$emit('exported', { exports: event.detail.exports, clientWidth: this.clientWidth, clientHeight: this.clientHeight, });
  
  if (this.exports && this.exports.pptx && this.saveRequested) {
    const blob = new Blob([this.exports.pptx], { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' });
    FileSaver.saveAs(blob, 'myscript-diagram.pptx'); this.saveRequested = false;
  }
}

Управляйте связью с другими компонентами

Мы используем шину событий для обеспечения связи между компонентами. Мы всегда будем прикреплять слушателей к смонтированным вариантам.

Слушатели отмены / повтора / очистки используются для вызова соответствующих методов редактора и генерируются другими компонентами (например, при нажатии кнопки отмены):

EventBus.$on('undo', () => { this.editor.undo(); });

Мы не будем подробно объяснять каждого слушателя, поскольку код в этой части не требует пояснений, но мы сосредоточимся на событиях preview, save и thicknessUpdate.

Если пользователь нажимает кнопку Предварительный просмотр, шина событий выдаст preview, и редактор будет его прослушивать. Предварительный просмотр означает скрытие редактора и запрос экспорта с использованием метода редактора и запрошенных миметипов, если модель редактора содержит штрихи. В противном случае мы будем запрашивать только пустой файл svg.

EventBus.$on('preview', () => { 
  this.displayStyle = 'none';
  if (this.editor.model.rawStrokes.length > 0) {   this.editor.export_(['image/svg+xml','application/vnd.openxmlformats-officedocument.presentationml.presentation']);
  } else { 
     EventBus.$emit('clearSvg');
  }
});

Событие save запрашивается так же, как и событие preview. Здесь мы используем библиотеку под названием FileSaver, которая позволяет нам быстро сохранять файл в формате pptx. Мы вызываем сохранение, только если у нас уже есть экспорт, в противном случае мы вызываем метод export и указываем, что мы запросили сохранение. Если модель пуста, мы генерируем событие showNotification для нашего компонента уведомления, используемого для отображения информационных сообщений.

EventBus.$on('save', () => { 
  if (this.exports && this.exports.pptx) { 
    const blob = new Blob([this.exports.pptx], 
      { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' });
    FileSaver.saveAs(blob, 'myscript-diagram.pptx'); 
  } else if (this.editor.model.rawStrokes.length > 0) { 
    this.editor.export_();
    this.saveRequested = true;
  } else { 
    EventBus.$emit('showNotification', 'save');
  }
});

Последнее событие, которое мы объясним, - это thicknessUpdate. Это происходит, когда пользователь меняет толщину пера. На слушателе мы используем полезную нагрузку, чтобы получить значение толщины. Затем мы можем установить атрибут penStyle редактора с цветом и толщиной, представленными -myscript-pen-width.

EventBus.$on('thicknessUpdated', (data) => { 
  this.currentThickness = data.value;
  this.editor.penStyle = 
    { color: this.currentColor ? this.currentColor : '', '-myscript-pen-width': this.currentThickness, }; 
});

Навбар

Панель навигации содержит все основные действия, которые мы можем использовать для взаимодействия с редактором. В этой статье мы не будем объяснять все элементы управления, поскольку они просты и часто полагаются на создание события, чтобы сообщить редактору об изменении.

Для примера возьмем настройки пера и сосредоточимся на параметрах цвета пера. Остальные примеры можно найти в этом каталоге на GitHub.

Сам цвет пера - это компонент, используемый внутри компонента PenSettings. Мы определяем массив цветов, содержащий наши предустановки цветов, в данные PenSettings.

data() { 
  return { colors: ['#000000', '#808080', '#D9D9D9', '#1A8CFF', '#FF1A40', '#2BD965', '#FFDD33'], ... };
}

Затем массив colors будет использоваться для определения цвета пера в шаблоне PenSettings.

<template>
  <div class="nav-group"> ... 
     <div class="colors">
       <pen-color v-for="color in colors" :color="color" :checked="color === '#000000'" :key="color"/>
       <color-picker/>
    </div>   
  </div>
</template>

Давайте углубимся в компонент PenColor. Шаблон цвета пера состоит из кнопки для определения щелчка и диапазона, представляющего галочку, чтобы сообщить пользователю, какой цвет выбран.

<template>
  <div class="pensettings">
     <button @click="changeColor" class="color" :style="style">
       <span v-if="colorChecked" class="check" :style="{ borderColor: tickColor }"></span>
     </button>
   </div>
</template>

Цвет фона управляется вычисляемым свойством с использованием свойства цвета, которое мы передали в PenSettings.

computed: { style() { return `background-color: ${this.color}`; } }

При щелчке мы используем метод changeColor(), который генерирует событие для редактора, а затем управляет цветом галочки, используя яркость цвета.

changeColor() { 
  EventBus.$emit('colorChanged', this.color);
  this.tickColor = blackOrWhiteTick(hexToRgb(this.color));
  this.colorChecked = true;
}

Наконец, мы хотим снять отметку с цвета компонента PenColor, если выбран другой цвет. Для этого мы используем шину событий, как показано ниже:

mounted() { 
  EventBus.$on('colorChanged', () => { this.colorChecked = false; }); 
}

Заключение

Что мы сделали:

  • Как интегрировать MyScriptJS в прогрессивный фреймворк JavaScript
  • Как взаимодействовать с редактором
  • Как использовать экспортированные данные в реальном сценарии использования

Полный исходный код доступен на GitHub, откуда вы можете собрать и запустить приложение.

10 июля 2018

Первоначально опубликовано на developer.myscript.com.