5
0

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 3 years have passed since last update.

一人LiveViewAdvent Calendar 2021

Day 8

Phoenixで作るGPS Logging System 8 Bulmaでスタイリング

Last updated at Posted at 2021-12-17

はじめに

ひとりLiveView Advent Calendar の8日目の記事です

この記事はElixir Conf US 2021発表したシステムの構築と関連技術の解説を目的とした記事です

今回はBulmaを使用してデザインを整えていきます

  • Bulmaの読み込み
  • Layout
  • TopPage
  • phx.gen.auth
  • phx.gen.live

Bulmaの読み込み

トレンドからは少しずれていますがBulmaでスタイリングしていきます
npmでもいいですが、本番環境ではないのでcdnで十分でしょう
phoenix.cssは競合するので使用しないようにします

assets/css/app.css
- @import "./phoenix.css";
+ @import "https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css";

layoutファイルのスタイリング

phoenix.cssとmiligramを消したので現在こんな感じです
スクリーンショット 2021-12-17 20.04.59.png
プロジェクト全体のレイアウトはroot.html.heexです
bulmaでheader部分をスタイリングしていきます
phoenixロゴではなくてアプリ名に変えます

lib/live_logger_web/templates/layout/root.html.heex
<!DOCTYPE html>
<html lang="en">
  <head>
    ...
  </head>
  <body>
    <header class="container is-fluid mb-5 pl-0 pr-0">
      <section class="hero is-link is-small">
        <nav role="navigation" class="navbar hero-body" aria-label="main navigation">
          <div class="navbar-brand">
            <h1 class="navbar-item title">
              <a href="/">LiveLogger</a>
            </h1>
          </div>
          <div class="navbar-end">
            <%= render "_user_menu.html", assigns%>
          </div>
        </nav>
      </section>
    </header>
    <%= @inner_content %>
  </body>
</html>

headerのリンクもリストからボタングループに変更します

lib/live_logger_web/templates/layout/_user_menu.html.heex
<div class="navbar-item">
  <div class="buttons">
    <%= if @current_user do %>
      <%= link "Maps", to: Routes.map_index_path(@conn, :index), class: "button"%>
      <%= link "Settings", to: Routes.user_settings_path(@conn, :edit), class: "button" %>
      <%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete, class: "button" %>
    <% else %>
      <%= link "Register", to: Routes.user_registration_path(@conn, :new), class: "button" %>
      <%= link "Log in", to: Routes.user_session_path(@conn, :new), class: "button" %>
    <% end %>
  </div>
</div>

LiveView時の追加のレイアウトです、containerとis-fluidで余白をもたせて全体に広げています

lib/live_logger_web/templates/layout/live.html.heex
<main role="main" class="container is-fluid">
...
</main>

スクリーンショット 2021-12-17 20.06.24.png

Top Page

トップページを変更しましょうhero unitでアプリ名とサブタイトルをつけます

lib/live_logger_web/templates/page/index.html.heex
<section class="hero">
  <div class="hero-body">
    <p class="title">LiveLogger</p>
    <p class="subtitle">Real-Time Update Multi-Client GPS Logging Viewer</p>
  </div>
</section>

スクリーンショット 2021-12-17 22.28.29.png

phx.gen.auhまわり

こちらもデザインが崩れているのでいい感じにしていきます

スクリーンショット 2021-12-17 20.24.35.png
以下のようにスタイリングすると画面の半分の幅で中央寄せにしてくれます
column is-centerered is-mobile
column is-half

フォームは基本的にfiledで囲って、label,inputのクラスを付与します
submitにbuttonクラスを付与します

lib/live_logger_web/templates/user_registration/new.html.heex
<div class="columns is-centered is-mobile">
  <div class="column is-half content">
    <h1>Register</h1>
    <div class="box">
      <.form let={f} for={@changeset} action={Routes.user_registration_path(@conn, :create)}>
        <%= if @changeset.action do %>
          <div class="alert alert-danger">
            <p>Oops, something went wrong! Please check the errors below.</p>
          </div>
        <% end %>

        <div class="field">
        <%= label f, :email, class: "label" %>
        <%= email_input f, :email, required: true, class: "input" %>
        <%= error_tag f, :email %>
        </div>

        <div class="field">
          <%= label f, :password, class: "label" %>
          <%= password_input f, :password, required: true, class: "input" %>
          <%= error_tag f, :password %>
        </div>
        <div class="control">
          <%= submit "Register", class: "button is-primary" %>
        </div>
      </.form>

      <p class="mt-5">
        <%= link "Log in", to: Routes.user_session_path(@conn, :new) %> |
        <%= link "Forgot your password?", to: Routes.user_reset_password_path(@conn, :new) %>
      </p>
    </div>
  </div>
</div>

スクリーンショット 2021-12-17 20.58.30.png
だいたいさっきと同じです

lib/live_logger_web/templates/user_session/new.html.heex
<div class="columns is-centered is-mobile">
  <div class="column is-half content">
    <h1>Log in</h1>
    <div class="box">
      <.form let={f} for={@conn} action={Routes.user_session_path(@conn, :create)} as={:user}>
        <%= if @error_message do %>
          <div class="alert alert-danger">
            <p><%= @error_message %></p>
          </div>
        <% end %>

        <div class="field">
          <%= label f, :email, class: "label" %>
          <%= email_input f, :email, required: true, class: "input" %>
          <%= error_tag f, :email %>
        </div>

        <div class="field">
          <%= label f, :password, class: "label" %>
          <%= password_input f, :password, required: true, class: "input" %>
          <%= error_tag f, :password %>
        </div>

        <div class="field">
          <%= checkbox f, :remember_me %>
          <%= label f, :remember_me, "Keep me logged in for 60 days", class: "checkbox" %>
          
        </div>

        <div class="control">
          <%= submit "Log in", class: "button is-primary" %>
        </div>
      </.form>

      <p class="mt-5">
        <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
        <%= link "Forgot your password?", to: Routes.user_reset_password_path(@conn, :new) %>
      </p>
    </div>
  </div>
</div>

スクリーンショット 2021-12-17 20.58.35.png

lib/live_logger_web/templates/user_reset_password/new.html.heex
<div class="columns is-centered is-mobile">
  <div class="column is-half content">
    <h1>Forgot your password?</h1>
    <div class="box">
      <.form let={f} for={:user} action={Routes.user_reset_password_path(@conn, :create)}>
        <div class="field">
        <%= label f, :email, class: "label" %>
        <%= email_input f, :email, required: true, class: "input" %>
        </div>
        <div class="field">
          <%= submit "Send instructions to reset password", class: "button is-primary" %>
        </div>
      </.form>

      <p class="mt-5">
        <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
        <%= link "Log in", to: Routes.user_session_path(@conn, :new) %>
      </p>
    </div>
  </div>
</div>

スクリーンショット 2021-12-17 20.58.24.png

lib/live_logger_web/templates/user_settings/edit.html.heex
<div class="columns is-centered is-mobile">
  <div class="column is-half content">
    <h1>Settings</h1>

    <h3>Generate Passcode</h3>
    <%= link "Generate Passcode", 
              to: Routes.user_settings_path(@conn, :generate_passcode), 
              method: :post, 
              class: "button is-info" 
    %>

    <%= if @conn.assigns.current_user.passcode do %>
      <p><%= "passcode:" <> @conn.assigns.current_user.passcode %></p>
    <% end %>

    <h3>Change email</h3>

    <.form let={f} for={@email_changeset} action={Routes.user_settings_path(@conn, :update)} id="update_email">
      <%= if @email_changeset.action do %>
        <div class="alert alert-danger">
          <p>Oops, something went wrong! Please check the errors below.</p>
        </div>
      <% end %>

      <%= hidden_input f, :action, name: "action", value: "update_email" %>

      <%= label f, :email, class: "label" %>
      <%= email_input f, :email, required: true, class: "input" %>
      <%= error_tag f, :email %>

      <%= label f, :current_password, for: "current_password_for_email", class: "label" %>
      <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email", class: "input" %>
      <%= error_tag f, :current_password %>

      <div class="mt-5">
        <%= submit "Change email", class: "button is-info" %>
      </div>
    </.form>

    <h3>Change password</h3>

    <.form let={f} for={@password_changeset} action={Routes.user_settings_path(@conn, :update)} id="update_password">
      <%= if @password_changeset.action do %>
        <div class="alert alert-danger">
          <p>Oops, something went wrong! Please check the errors below.</p>
        </div>
      <% end %>

      <%= hidden_input f, :action, name: "action", value: "update_password" %>

      <%= label f, :password, "New password", class: "label" %>
      <%= password_input f, :password, required: true, class: "input" %>
      <%= error_tag f, :password %>

      <%= label f, :password_confirmation, "Confirm new password", class: "label" %>
      <%= password_input f, :password_confirmation, required: true, class: "input" %>
      <%= error_tag f, :password_confirmation %>

      <%= label f, :current_password, for: "current_password_for_password", class: "label" %>
      <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password", class: "input" %>
      <%= error_tag f, :current_password %>

      <div class="mt-5">
        <%= submit "Change password", class: "button is-info" %>
      </div>
    </.form>
  </div>
</div>

スクリーンショット 2021-12-17 22.57.30.png

最後にログイン後に/mapsに飛ぶように指定します

lib/live_logger_web/controllers/user_auth.ex
defmodule LiveLoggerWeb.UserAuth do
  ...
  defp signed_in_path(_conn), do: "/maps"
end

phx.gen.live まわり

phx.gen.live周りはグリッドシステムは使用せずに基本的にcontentクラスをつけるくらいです

lib/live_logger_web/live/map_live/index.html.heex
<div class="content">
  <h1>Listing Maps</h1>

  <%= if @live_action in [:new, :edit] do %>
    <%= live_modal LiveLoggerWeb.MapLive.FormComponent,
      id: @map.id || :new,
      title: @page_title,
      action: @live_action,
      map: @map,
      return_to: Routes.map_index_path(@socket, :index) %>
  <% end %>

  <table class="table">
    <thead>
      <tr>
        <th>User</th>
        <th>Name</th>
        <th>Description</th>

        <th></th>
      </tr>
    </thead>
    <tbody id="maps">
      <%= for map <- @maps do %>
        <tr id={"map-#{map.id}"}>
          <td><%= map.user.email %></td>
          <td><%= map.name %></td>
          <td><%= map.description %></td>
          <td>
            <span><%= live_redirect "Show", to: Routes.map_show_path(@socket, :show, map) %></span>
            <span><%= live_patch "Edit", to: Routes.map_index_path(@socket, :edit, map) %></span>
            <span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: map.id, data: [confirm: "Are you sure?"] %></span>
          </td>
        </tr>
      <% end %>
    </tbody>
  </table>

  <span><%= live_patch "New Map", to: Routes.map_index_path(@socket, :new) %></span>
</div>

スクリーンショット 2021-12-17 22.59.13.png
formはfield,label,input,buttonを付与します

lib/live_logger_web/live/map_live/form_component.html.heex
<div class="content">
  <h2><%= @title %></h2>

  <.form
    let={f}
    for={@changeset}
    id="map-form"
    phx-target={@myself}
    phx-change="validate"
    phx-submit="save">
  
    <div class="field">
      <%= label f, :name, class: "label" %>
      <%= text_input f, :name, class: "input" %>
      <%= error_tag f, :name %>
    </div>

    <div class="field">
      <%= label f, :description, class: "label" %>
      <%= text_input f, :description, class: "input" %>
      <%= error_tag f, :description %>
    </div>  

    <%= hidden_input f, :user_id %>

    <div class="field">
      <%= submit "Save", phx_disable_with: "Saving...", class: "button is-info" %>
    </div>
  </.form>
</div>

スクリーンショット 2021-12-17 22.59.35.png
Map詳細画面はサイドバーとメイン画面に分けます

lib/live_logger_web/live/map_live/show.html.heex
<div class="columns">
  <div class="column is-2 content">
    <h1>Show Map</h1>
    <span><%= live_patch "Edit", to: Routes.map_show_path(@socket, :edit, @map), class: "button is-info" %></span>
    <span><%= live_redirect "Back", to: Routes.map_index_path(@socket, :index), class: "button" %></span>
    <ul>
      <li>
        <strong>Name:</strong>
        <%= @map.name %>
      </li>
      <li>
        <strong>Description:</strong>
        <%= @map.description %>
      </li>
    </ul>    
  </div>
  <div class="column is-10">
    <%= if @live_action in [:edit] do %>
      <%= live_modal LiveLoggerWeb.MapLive.FormComponent,
        id: @map.id,
        title: @page_title,
        action: @live_action,
        map: @map,
        return_to: Routes.map_show_path(@socket, :show, @map) %>
    <% end %>

    <div id="googleMap" phx-update="ignore" phx-hook="Map">
      <div id="map"></div>
    </div>
  </div>
</div>

スクリーンショット 2021-12-17 23.07.50.png

最後に

だいたいいい感じになりました
実際に使うのはlayoutとshow.html.heexくらいなので、ほかは飛ばしても構いません

次はPhoenixから少し離れてGPS LoggerのクライアントをExpoでつくります

コード

5
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?