5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Livebook で Web スライドを作る

Last updated at Posted at 2023-04-24

はじめに

ElixirConf EU 2023 での発表を無事終え、精神のキャパシティーが空いたので久しぶりに投稿します

今回は Livebook のデプロイ機能を使って、 Web スライドを作ります

役に立つ場面はないと思いますが、こんなこともできるよ、というネタです

実装したノートブックはこちら

実行環境

  • Elixir 1.14.2
  • Livebook 0.9.2

セットアップ

セットアップセルに以下のコードを入力し、実行します

Mix.install([
  {:kino, "~> 0.9.2"}
])

今回使うのは Kino だけです

スライドショーモジュールの定義

以下のようにスライドショーモジュールを定義します

defmodule SlideShow do
  use GenServer

  @slides [
    """
    # 大分県

    ## Oita, Japan

    ![大分県](https://1.bp.blogspot.com/-uEqcqdspoIE/WMJLJPr0C5I/AAAAAAABCd8/mXaaXFIAv8MD9qWwsQ1oR196-DIbWL2QwCLcB/s400/japan_character8_kyuusyuu5_ooita.png)
    """,
    """
    ## 日本一のおんせん県おおいた

    Oita, the best hot-spring resort in Japan

    - 源泉総数 Total number of springs: **5,093**
    - 湧出量 Hot spring output: **298,264** l / minutes 


    """,
    """
    <div style="transform: scale(1.3)">

    ## 絶品グルメ

    Excellent Gourmet

    </div>

    <div style="transform: scale(1.2)">

    ### とり天

    Chicken tempura

    ### 中津唐揚げ

    Nakatsu flied chiken

    ### 関あじ・関さば

    Seki horse mackerel and Seki mackerel

    </div>
    """,
    """
    ## 宇宙

    ## Space

    > 大分空港は、宇宙港(スペースポート)として活用することとなりました。
    >
    > 大分空港からは、航空機が人工衛星を搭載したロケットを翼に吊り下げて、空港から飛び立ち、空中でロケットを打ち上げます。

    > Oita Airport is to be utilized as a spaceport.
    > 
    > From Oita Airport, an aircraft will take off from the airport with a rocket carrying a satellite suspended from its wings and launch the rocket in mid-air.
    """,
    """
    # ぜひ来てください

    # Come and visit us!
    """
  ]

  @page_count Enum.count(@slides)

  def init(default_page) do
    {:ok, default_page}
  end

  def show_slide(page) do
    @slides
    |> Enum.at(page)
    |> then(fn content ->
      """
      <div class='flex flex-col justify-center items-center p-8' style='height: 600px'>

      """
      <> content <>
      """
      </div>
      """
    end)
    |> Kino.Markdown.new()
  end

  def handle_call(:current, _from, current_page) do
    {:reply, show_slide(current_page), current_page}
  end

  def handle_call(:next, _from, current_page) when current_page >= @page_count - 1 do
    {:reply, show_slide(0), 0}
  end

  def handle_call(:next, _from, current_page) do
    {:reply, show_slide(current_page + 1), current_page + 1}
  end

  def handle_call(:prev, _from, current_page) when current_page == 0 do
    {:reply, show_slide(@page_count - 1), @page_count - 1}
  end

  def handle_call(:prev, _from, current_page) do
    {:reply, show_slide(current_page - 1), current_page - 1}
  end
end

解説は後で行います

スライドショーの開始

GenServer.start_link でスライドショーのプロセスを開始します

第1引数にスライドショーモジュール、第2引数に初期ページとして 0 を指定します

{:ok, slide} = GenServer.start_link(SlideShow, 0)

画面の定義

スライドを表示するための枠を作ります

frame = Kino.Frame.new()

スクリーンショット 2023-04-24 13.01.48.png

この時点では空の枠で、 Nothin here... と表示されます

初期表示として現在のスライドを表示し、ページ移動のためのボタンを作ります

Kino.Frame.render(frame, GenServer.call(slide, :current))

prev_button = Kino.Control.button("<")
next_button = Kino.Control.button(">")

[prev_button, next_button]
|> Kino.Layout.grid(columns: 8)

スクリーンショット 2023-04-24 13.03.42.png

Kino.Layout.grid(columns: 8) によってボタンを横並びで左端に寄せています

動きの定義

prev_button をクリックしたら前ページ、next_button をクリックしたら次ページを表示するようにします

[prev: prev_button, next: next_button]
|> Kino.Control.tagged_stream()
|> Kino.listen(fn
  {:prev, _} ->
    Kino.Frame.render(frame, GenServer.call(slide, :prev))
  {:next, _} ->
    Kino.Frame.render(frame, GenServer.call(slide, :next))
end)

これを実行した時点でボタンによるページ移動ができるようになります

Apr-24-2023 13-08-52.gif

デプロイ

左メニューからロケットアイコンをクリックし、デプロイメニューを表示します

スクリーンショット 2023-04-24 13.10.53.png

Slug に slide (任意の文字列)を入れて、 Deploy をクリックします

下に DEPLOYMENTS が表示され、しばらくすると Status が Running になります

スクリーンショット 2023-04-24 13.12.04.png

/app/slide をクリックすると、以下のような画面が開き、 Web スライドとして動作します

Apr-24-2023 13-15-49.gif

解説

以降、 SlideShow モジュールの解説をします

状態管理

GenServer を使うことでスライドの現在ページを保持します

スライドの内容定義

SlideShow モジュールの @slides に文字列のリストでスライドの内容を定義しています

ここを書き換えることで任意のスライドショーが作成できます

内容は Markdown で記述しています

従って、見出しは # 見出し## 小見出し のように記述します

    # 大分県

また、 ![<alt>](<画像URL>) で画像、 - で列挙、 > で引用が表現できます

    ![大分県](https://1.bp.blogspot.com/-uEqcqdspoIE/WMJLJPr0C5I/AAAAAAABCd8/mXaaXFIAv8MD9qWwsQ1oR196-DIbWL2QwCLcB/s400/japan_character8_kyuusyuu5_ooita.png)
...
    - 源泉総数 Total number of springs: **5,093**
    - 湧出量 Hot spring output: **298,264** l / minutes 
...
    > 大分空港は、宇宙港(スペースポート)として活用することとなりました。
    >
    > 大分空港からは、航空機が人工衛星を搭載したロケットを翼に吊り下げて、空港から飛び立ち、空中でロケットを打ち上げます。

更に HTML を埋め込むことも可能なので、 <div style="transform: scale(1.3)"></div> で囲むことで、対象範囲内を拡大表示する、といったこともできます

    <div style="transform: scale(1.3)">

    ## 絶品グルメ

    Excellent Gourmet

    </div>

show_slide 関数で指定ページのスライドを Markdown として解釈し、画面に表示します

  def show_slide(page) do
    @slides
    |> Enum.at(page)
    |> then(fn content ->
      """
      <div class='flex flex-col justify-center items-center p-8' style='height: 600px'>

      """
      <> content <>
      """
      </div>
      """
    end)
    |> Kino.Markdown.new()
  end

Livebook では Tailwind CSS を使っているため、ある程度の(Livebook で使っている)  Tailwind CSS のクラスが使えます

class='flex flex-col justify-center items-center p-8' により、中央寄せと余白を作っています

また、 style='height: 600px' により、スライドの高さを固定しています

(h-[600px] のように固定値を指定する Tailwind CSS のクラスは効かないので style 属性で指定しています)

ページ移動

以下のコードで現在ページを表示しています

handle_call の戻り値タプルの2番目が実際に結果として返る値、3番目が保持する状態です

:current では状態が変化しないので、 current_page をそのまま返します

  def handle_call(:current, _from, current_page) do
    {:reply, show_slide(current_page), current_page}
  end

以下のコードで次ページを表示します

when current_page >= @page_count - 1 によって、最終ページの場合は先頭に戻るようにしています

  def handle_call(:next, _from, current_page) when current_page >= @page_count - 1 do
    {:reply, show_slide(0), 0}
  end

  def handle_call(:next, _from, current_page) do
    {:reply, show_slide(current_page + 1), current_page + 1}
  end

以下のコードで前ページを表示します

when current_page == 0 によって先頭ページの場合は最終ページに戻るようにします

  def handle_call(:prev, _from, current_page) when current_page == 0 do
    {:reply, show_slide(@page_count - 1), @page_count - 1}
  end

  def handle_call(:prev, _from, current_page) do
    {:reply, show_slide(current_page - 1), current_page - 1}
  end
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?