Здравствуйте, меня зовут Дмитрий Карловский и я иногда выступаю на конференциях, митапах, а так же с недавних пор сам вхожу в команду организаторов одного из них — PiterJS. Недавно у нас был юбилей — 40 проведённых митапов. Но вместо того, чтобы расслабиться и получать поздравления, мы запарились и сами подготовили доклады от организаторов.


Тестируем голосовое управление


Но и этого нам мало, поэтому мы решили отметить юбилей по крупному, организовав конференцию на берегах Невы PiterJSConf, которая пройдёт уже в эту субботу 7 сентября 2019. Спешите записываться, пока ещё есть свободные места, ведь участие в ней для вас будет совершенно бесплатно.


Мы всё это делаем не за деньги, а за великую идею, что знания должны быть бесплатны. Поэтому всё, что мы делаем, доступно в Open Source. Мы с радостью делимся своими наработками, знаниями и опытом с другими. И призываем к сотрудничеству организаторов из других городов для создания открытой платформы организации технологических митапов на регулярной основе. Присоединяйтесь к нам в качестве организатора, партнёра, докладчика, волонтёра, патрона или просто слушателя.


А пока, предлагаю вам рассказ про веб приложение для проведения презентаций $hyoo_slides, которое я использую для всех своих выступлений. Видеозапись доступна на YouTube, но там не всё. Можете читать этот рассказ как статью, так и открыть в интерфейсе самого приложения. Далее я расскажу вам, сколько всего оно умеет, и как работает.


Интерфейс слушателя


Презентация в режиме слушателя


Перед вами интерфейс, который видят слушатели во время выступления. В нём нет ничего лишнего.


Сверху выводится название и номер слайда. У слушателей могут возникнуть вопросы. И быстро записав этот номер, они смогут после выступления назвать его и тем самым избавить аудиторию от утомительного ожидания, когда же докладчик найдёт слайд, по которому задаётся вопрос.


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


Смотри как я могу


Основная идея в том, чтобы позволить докладчику сконцентрироваться на содержимом и не беспокоиться об оформлении. Докладу вовсе не нужны какие-то особые красоты и эффектные анимации. Иначе оформление будет перетягивать внимание на себя. А содержание рискует пролететь мимо ушей.


Потому, что я - Бэтмен!


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


Интерфейс докладчика


Интерфейс докладчика разделён на две части.


Презентация в режиме докладчика


Слева отображается то, что видят слушатели. А справа — заметки докладчика. Они помогут вам вспомнить потерянную мысль, не отворачиваясь от слушателей и не напрягая их долгой… эм… это… паузой.


Главное — контент


Поэтому в нашем случае контент пишется как статья в формате MarkDown и выкладывается на какой-нибудь GitHub Pages. А всё остальное берёт на себя веб-приложение.


# Название первого слайда

Содержимое первого слайда

# Название второго слайда

Содержимое второго слайда

После выступления текстовую расшифровку доклада часто выкладывают в виде статьи на каком-нибудь Хабре. Делать её могут месяц, два, пол года. Задача это неблагодарная переводить речь в текст. Но, благодаря тому, что исходники слайдов уже в формате markdown и содержат комментарии докладчика, их в таком виде можно сразу публиковать.


Стили текста


Обычный текст отображается только докладчику. А всякие картинки, списки, таблицы, цитаты и тп штуки видны всем.


- *Акцент*
- **Сильный акцент**
- ~~Удаление~~
- ```Код```

  • Акцент
  • Сильный акцент
  • Удаление
  • Код

Разумеется доступны и различные средства инлайн форматирования.


Исходные коды


Блоки исходного кода, разумеется, тоже поддерживаются, и раскрашиваются во все цвета радуги. Прям как вы любите.


```javascript
const hello = ()=>
<body>
    Hello, "world"!
</body>
```

const hello = ()=>
<body>
    Hello, "world"!
</body>

Таблицы


Сравнивать различные штуки вам помогут таблицы. Например, давайте посмотрим, чем $hyoo_slides лучше ближайших конкурентов — shwr.me и google slides


|                          | shwr.me | google slides | slides.hyoo.ru |
|--------------------------|---------|---------------|----------------|
| Исходник в MarkDown      | -       | -             | +              |
| Двухпанельный режим      | -       | +             | +              |
| Автопереключение слайдов | -       | -             | +              |
| Оффлайн                  | -       | +             | +              |

shwr.me google slides slides.hyoo.ru
Исходник в MarkDown - - +
Двухпанельный режим - + +
Автопереключение слайдов - - +
Оффлайн - + +

Как видно, $hyoo_slides бьёт конкурентов по всем фронтам. Кроме тех, которые не вошли в таблицу, конечно же.


Ну да лано, таблицы — это скучно.


Картинки


Держите котейку.


![Котейка](https://github.com/nin-jin/slides/raw/master/slides/cat.gif)

Котейка


Обратите внимание, что фон слайдов слегка серый. Поэтому изображения лучше готовить не на белом фоне, а с прозрачностью, чтобы вокруг картинок не было неприятного белого прямоугольника. Сделано это, чтобы белые области смотрелись контрастно, а не сливались с фоном.


Видео


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


![Нецелевая аудитория](https://www.youtube.com/embed/exfBX2pb7AQ?autoplay=1)

Нецелевая аудитория


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


Клавиатурная навигация


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


Стрелочки


Но что если кликер сломался?


Кодовые фразы


Вы, наверно, думаете, что в зале у меня тут где-то есть засланный казачок, который переключает слайды вместо меня? Однако, это не так.


  • Дальше, пожалуйста.
  • Назад, пожалуйста.
  • Слайд номер 5, будь добра.
  • На начало, пожалуйста.
  • В конец, пожалуйста.
  • Найди "котейку", будь добра.
  • Повтори, пожалуйста.
  • Помолчи, будь любезна.
  • Продолжай, пожалуйста.
  • Выключи свет, будь любезна.

Повтори, пожалуйста. Голос свыше повторяет последнюю фразу.


Да, слайдами можно полностью управлять голосом, оставляя свои руки свободными для жестикуляций. Тут используется стандартное веб-апи для распознавания и синтеза голоса.


Но повторять эти кодовые фразы по десять раз подряд — это скучно, поэтому $hyoo_slides умеет анализировать заметки докладчика и, когда вы произносите последнее слово, переключать слайд автоматически.


Жесты пальцем


Ладно, усложняем ситуацию. Кто-то наслушавшись вашего выступления, решил посмотреть ваши слайды с планшета, пока едет в метро домой.


Жесты


Клавиатуры нет. Поезда шумят. Тут на выручку приходят обычные пальцевые жесты.


Оффлайн


Но тут он заезжает в тоннель и у него пропадает связь.


Нет сети


Не беда, у нас же Web2.0 HTML5 Progressive Web Application с полной работоспособностью даже, когда нет интернета.


Печать в PDF


Но тут к вам подходят организаторы и говорят: "Хотим PDF".


Отпечаток


Какой PDF? У нас же тут мультимедийный интерактивный Web2.0 HTML5 Progressive Web Application. Однако, вам объясняют, что приложение сегодня есть, а завтра его уже нет. А если есть, то хочет денежку. А если не хочет, то слайды там уже могут быть изменены до неузнаваемости. А PDF лежит тихонько в архиве ровно с тем содержимым, которое соответствует записанному во время выступления видео.


Ну что ж, не беда, жмём Ctrl+P, выбираем "Печать в PDF" и получаем то, что нужно. Делается это просто — отслеживается событие onbeforeprint и, когда оно возникает, вместо одного лишь текущего слайда рендерятся вообще все слайды. А на onafterprint, все, кроме текущего, слайды удаляются.


На этом списков возможностей пока что закончен.


Как создать презентацию


Попробовать в деле $hyoo_slides очень просто. Вам потребуется readme.md с вашим контентом и картинки. Так же рядом нужно будет скопипастить index.html, который редиректнет на веб приложение и откроет вашу презентацию в нём. А так же offline.js для поддержки оффлайна.



Имейте ввиду, что этот index.html будет выдавать приложению любые файлы, доступные с того домена, куда вы всё это дело выложите. GitHub Pages — вполне удобный и безопасный вариант. Сам его использую.


Другие приложения


Если вам понравилось это приложение, то можете глянуть и другие интересные приложения, реализованные на фреймворке $mol. Они настолько легковесные, что даже несколько десятков их не страшно загрузить разом на одном слайде.


Галерея приложений


Но о них как-нибудь потом…


Примеры презентаций


Подробнее о фреймворке можно узнать на отдельной презентации. Копнуть глубже можно в презентации посвящённой ОРП. А приподнять завесу грядущего можно в презентации о квантовании вычислений.



Все они используют $hyoo_slides для отображения. Надеюсь вскоре таких презентаций станет больше.


Структура приложения


А сейчас, давайте приоткроем капот и посмотрим, как устроено приложение, и как сделать своё аналогичное всего за один вечер.


$hyoo_slides_page $mol_view
    sub /
        <= Listener
        <= Speaker

export class $hyoo_slides_page extends $mol_view {

    sub() { return [
        this.Listener() ,
        this.Speaker() ,
    ] }

}

Перед вами верхнеуровневое описание одного экрана на языке view.tree и эквивалентный код на TypeScript. Тут мы объявляем компонент $hyoo_slides_page, который расширяет базовый компонент $mol_view. У этого компонента есть свойство sub. Всё, что возвращает это свойство, будет отрендерено внутри компонента. Поэтому мы переопределяем его, передавая в качестве значения массив из двух элементов: Listener — компонент вывода слайда слушателям и Speaker — компонент дополнительной панели докладчика.


Переключение раскладки страницы


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


sub() {

    const role = this.role()

    return [
        this.Listener() ,
        ... ( role === 'speaker' ) ? [ this.Speaker() ] : [] ,
    ]

}

Тут логика у нас простая: слайды для слушателей выводим всегда, а вот панель докладчика показываем только, если текущая роль — speaker. Если роль изменится, то и раскладка приложения тоже изменится благодаря магии объектного реактивного программирования.


Роутинг


Роль мы будем брать из параметра адреса, через специальный реактивный API $mol_state_arg.


role() : 'speaker' | 'listener' {
    return $mol_state_arg.value( 'role' ) || 'speaker'
}

По какой бы причине ни поменялся адрес — роль будет извлечена из него автоматически, и проведена через в этот метод.


Структура интерфейса слушателя


Давайте опишем интерфейс слушателя.


Listener $mol_page

    title <= title

    tools /
        <= Slide_switcher

    body /
        <= Listener_content
        <= Progress

Он использует стандартный компонент $mol_page который рисует типичную страницу с шапкой и телом. В шапке есть область, куда выводится название страницы. Через свойство title можно указывать, что туда выводить. Что мы и сделали, связав его свойство title с нашим одноимённым свойством. Теперь, меняя наше свойство, мы полностью контролируем, что будет выводиться на странице в качестве заголовка.


Справа в шапке, есть область вывода дополнительных инструментов — tools. В неё мы выводим Slides_switcher — компонент для отображения номера слайда и переключения между соседними слайдами.


И, наконец, в качестве тела страницы в body мы выводим содержимое слайда и прогресс бар.


Структура переключателя страниц


Как же реализовать Slide_switcher? Просто используем стандартный компонент $mol_paginator.


Slide_switcher $mol_paginator
    value?val <=> slide?val

Всё, что у него есть — это изменяемое свойство value, которое мы двусторонне связываем с нашим свойством, содержащим номер текущего слайда. Никаких импортов, колбэков, событий и прочего хлама. Эти две строчки — это всё, что необходимо, чтобы у вас на странице появился работающий переключатель страниц.


Структура содержимого слайда


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


Listener_content $mol_text

    uri_base <= uri_base

    text <= listener_content

Он принимает текст в формате markdown и визуализирует его. Так как ссылки в этом тексте будет относительно исходного файла, а не нашего приложения, то в свойство uri_base мы передаём ссылку, относительно которой будут резолвиться все пути.


Структура индикатора прогресса


Как вы уже, наверно, догадались для отображения прогресса тоже есть стандартный компонент — $mol_portion.


Progress $mol_portion
    portion <= progress

portion: [ 0 .. 1 ]

Скармливаем ему число от 0 до 1 и получаем заполненную на эту долю индикатор.


Структура интерфейса докладчика


В интерфейсе докладчика у нас есть кое-что по интересней. Отображаемые в шапке инструменты никак не привязаны к текущей странице — они общие для всего приложения. Поэтому вместо того, чтобы хардкодить их тут, мы разместим ту лишь слот speaker_tools, через который будем передавать список компонент извне.


Speaker $mol_page

    head <= speaker_tools /$mol_view

    body /
        <= Speaker_content

Структура приложения


Теперь поднимемся уровнем выше и создадим компонент приложения $hyoo_slides, который использует компонент страницы.


$hyoo_slides $mol_view

    Page!index $hyoo_slides_page
        - ...

    plugins /

        <= Nav
        <= Touch
        <= Speech_next

        - ...

У любого $mol_view компонента есть свойство plugins через которое можно подключать к нему дополнительную логику. Подключаемые плагины живут на том же DOM узле, что и сам компонент. Компонент их инициирует при своём рендеринге. А когда о перестаёт рендериться — плагины уничтожаются автоматически.


Также мы объявили свойство Page, которое для каждого индекса возвращает отдельный экземпляр разработанного нами ранее компонента $hyoo_slides_page.


Настройка страницы извне


Page!index $hyoo_slides_page

    role <= role

    slide?val <=> page_slide!index?val

    speaker_tools /

        <= Speech_toggle
        <= Speech_text
        <= Open_listener

Свойство role мы передаём в подкомпонент как есть. Свойство slide компонента страницы связываем двусторонней связью со свойством page_slide приложения. Обратите внимание, что page_slide принимает не только опциональное новое значение, но и индекс страницы. Это позволяет для каждой страницы возвращать свой номер. Наконец, в ранее объявленный нами слот speaker_tools мы помещаем три компонента, помогающие управлять слайдами.


Структура переключателя голосового управления


Speech_toggle мы реализуем через стандартный компонент $mol_check_icon, рисующий иконку. При клике на него, он переключает флаг checked. А текущее состояние отображается изменением цвета иконки.


Speech_toggle $mol_check_icon

    Icon <= Speech_toggle_icon $mol_icon_microphone

    checked?flag <=> speech_enabled?flag

Иконку мы взяли из пакета $mol_icon, где из 4000 иконок, выполненных в аскетичном material design стиле, легко найти нужную.


Структура кнопки открытия ведомого окна


Тут всё просто. Эта кнопка будет ссылкой $mol_link. Ей можно задать свойство uri с адресом, а можно поступить хитрее и просто пропатчить текущий адрес, заменив через arg некоторые параметры.


Listener_open $mol_link

    target \_blank

    arg *
        role \listener
        slide null

    sub /
        <= Listener_open_icon $mol_icon_external

Тут мы стёрли из адреса номер слайда, чтобы ведомое окно брало его из локального хранилища, а не из адреса. Это обеспечит синхронизацию окон друг с другом. А так же указали, что роль должна быть "слушатель". Внутрь ссылки вместо текста мы положили иконку.


Плагины


Плагины позволяют значительно расширить возможности компонента. Будем использовать их по максимуму.


plugins /

    <= Nav
    <= Touch
    <= Speech_next
    <= Speech_prev
    <= Speech_start
    <= Speech_end

    - ...

Все использованные нами плагины можно разделить на 3 категории: клавиатурная навигация, управление жестами и управление голосом.


Клавиатурная навигация


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


Nav $mol_nav

    keys_y <= slide_keys
    keys_x <= slide_keys

    current_y?val <=> slide?val
    current_x?val <=> slide?val

slide_keys: [ 0 , 1 , 2 , 3 , ... , 30 ]
                      ^
                    slide

Жесты пальцем


Для отслеживания пальцев есть плагин $mol_touch. С его помощью можно зумить, панорамировать и свайпать. Именно последняя возможность нас сейчас и интересует.


Touch $mol_touch

    swipe_to_left?event <=> go_next?event
    swipe_to_right?event <=> go_prev?event

go_next( event? : Event ) {
    this.slide( this.slide() + 1 )
}

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


Голосовое управление


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


Sing $mol_speech

    event_catch?val <=> sing?val

    patterns /
        \sing( \S+?)*
        \спой( \S+?)*

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


Автоматическое переключение слайдов


По умолчанию, $mol_speech требует вежливого обращения. Например, нужно говорить не "спой", а "спой, пожалуйста". Можно переопределить свойство suffix, чтобы изменить или вообще убрать это кодовое слово.


Speech_next_auto $mol_speech

    event_catch?val <=> go_next?val

    suffix 
    patterns <= speech_next_auto_patterns

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


Запуск приложения


Имея компонент приложения, можно инстанцировать его вручную как любой обычный класс. Но мы воспользуемся автоматическим запуском через специальный атрибут mol_view_root.


<body mol_view_root="$hyoo_slides">
    <script src="web.js" charset="utf-8"></script>
</body>

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


Оффлайн


Чтобы добавить поддержку оффлайна, достаточно включить в бандл модуль mol/offline/install.


include \/mol/offline/install

Он автоматически поднимает ServiceWorker, который кеширует все запросы. А в случае недоступности сети — выдаёт данные из кеша. Это не самая крутая реализация, но для простых случаев, когда не хочется заморачиваться, — подойдёт.


Режим печати


Ранее я говорил, что во время печати нужно рендерить все страницы, но вручную отслеживать onbeforeprint и onafterprint нет никакой необходимости, ведь у нас же реактивное программирование в полный рост, поэтому мы воспользуемся реактивным состоянием $mol_print, дающим нам реактивный флаг, к которому мы можем подвязать рендеринг.


sub() {

    if( !this.$.$mol_print.active() ) {
        return [ this.Page( this.slide() ) ]
    }

    return $mol_range2(
        index => this.Page( index ) ,
        ()=> this.slide_keys().length ,
    )

}

Тут мы возвращаем лишь одну страницу, если флаг active не поднят. А иначе возвращаем ленивый массив, вычисляющий свою длину и элементы по заданным формулам.


Эпилог


В итоге у нас получилось современное веб приложение с кучей функциональности и весом всего в 35кб. Добавить manifest и будет полноценное Progressive Web Application. Разумеется, многие детали не были рассмотрены. Увидеть их можно в коде на ГитХабе. По всем вопросам пишите телеграмы в чате.


Эти слайды в приложении: slides.hyoo.ru
Исходники приложения: hyoo-ru/slides.hyoo.ru
Примеры презентаций: nin-jin/slides
Телеграм чат: @mam_mol

Буду рад, если вы попробуете сделать свою презентацию с помощью $hyoo_slides. И буду совсем счастлив, если поможете сделать его ещё лучше, чем есть сейчас. Спасибо за внимание!

Комментарии (10)


  1. justboris
    01.09.2019 22:06

    Все здорово, но неясно как сделать свою презентацию на вашем движке.


    Беру этот репозиторий, клонирую себе, а дальше что? Куда контент добавлять?


    1. vintage Автор
      01.09.2019 22:46

      Движок живёт в интернете, кушать не просит.


      Примеры презентаций можно найти тут: https://github.com/nin-jin/slides/


  1. Alexufo
    02.09.2019 02:01

    Отлично. А как при распечатке в PDF ведет себя формат бумаги? PDF делается конвертацией через js либу или подразумевается печать через системный pdf? В последнем случае же размер берется полотна а4 и слайды коряво сохранятся (вынуждены будут или порваться по середине или быть с белыми полями)

    Для популяризации решения, к сожалению, я вижу проблемы из-за плохой кастомизации. Люди не соглашаются быть одинаковыми.Я понимаю суть — контент фёрст, но невозможность отступить от цветов, по которым угадывается мол, будет преследовать.

    Если будет файл с конфигурацией (theme.json), описывающий цвета, анимации переходов, набор гарнитуры, паддинги, возможность дописать кастомный стиль к какому либо семантическому элементу будет прям огонь.


    1. vintage Автор
      02.09.2019 07:02

      Да, используется системный. Слайды растягиваются на 100% вьюпорта, поэтому каждый слайд занимает ровно одну страницу. Разве что надо развернуть страницу в альбомный режим.


      Далеко не всем нужна кастомизация. А кому нужна — можно форкнуть приложение и сделать с ним всё, что угодно.


      В принципе, можно поддержать подключение кастомного css, да.


  1. babylon
    02.09.2019 04:55

    У меня на рабочем столе есть ярлык с ссылкой на комментарии vintage на хабре. Я периодически её кликаю. Вот и сегодня кликнул и увидел, что рейтинг с 14 внезапно поднялся до 20. Это могло означать только одно — у Дмитрия вышла новая статья. А это значит, что я получу новую порцию умных мыслей. Конечно мы все ждем доклада про $mol в Яндексе. Потому что одного хабра мне мало. Тем более его в Яндекс оригинально пригласили. Но возможно банально нет времени.


    Я не считаю что знания должны быть бесплатны. Потому, что есть те кто эти знания профессионально создаёт, транслирует и поддерживает. И делает это лучше чем что-то ещё. За это таких людей надо поощрять. А доступ к знаниям да может быть бесплатен.


    1. vintage Автор
      02.09.2019 07:05

      Пригласили — громко сказано. Скорее "мы вам перезвоним".


  1. KvanTTT
    02.09.2019 12:21

    У нас с вами схожие мысли на счет презентаций, год назад делился своими мыслями в статье Современный формат презентаций.


  1. Jabher
    02.09.2019 13:00

    не очень понял, чем это хоть сколько-то может быть удобнее что в долгой, что в короткой перспективе чем, например, mdx-deck, построенный на уже хорошо разросшейся и проверенной экосистеме react, gatsby и mdx.


    1. vintage Автор
      02.09.2019 17:02

      Ничем, расслабьтесь.


  1. babylon
    03.09.2019 08:03

    Утро началось с позитива — Андрееску в четвертьфинале!