Здравствуйте, меня зовут Дмитрий Карловский и я иногда выступаю на конференциях, митапах, а так же с недавних пор сам вхожу в команду организаторов одного из них — 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
для поддержки оффлайна.
readme.md
- картинки
index.html
offline.js
- статический хостинг
Имейте ввиду, что этот index.html
будет выдавать приложению любые файлы, доступные с того домена, куда вы всё это дело выложите. GitHub Pages — вполне удобный и безопасный вариант. Сам его использую.
Другие приложения
Если вам понравилось это приложение, то можете глянуть и другие интересные приложения, реализованные на фреймворке $mol. Они настолько легковесные, что даже несколько десятков их не страшно загрузить разом на одном слайде.
Галерея приложений
Но о них как-нибудь потом…
Примеры презентаций
Подробнее о фреймворке можно узнать на отдельной презентации. Копнуть глубже можно в презентации посвящённой ОРП. А приподнять завесу грядущего можно в презентации о квантовании вычислений.
- $mol — лучшее средство от геморроя
- Объектное Реактивное Программирование
- Quantum Mechanics of Calculations
- slides.hyoo.ru
Все они используют $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.
Телеграм чат: @mam_mol
Буду рад, если вы попробуете сделать свою презентацию с помощью $hyoo_slides
. И буду совсем счастлив, если поможете сделать его ещё лучше, чем есть сейчас. Спасибо за внимание!