Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/radiator/podcasts/podcast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ defmodule Radiator.Podcasts.Podcast do
has_many :episodes, Radiator.Podcasts.Episode do
description "The episodes of the podcast"
public? true
sort number: :desc_nils_first
end

belongs_to :license, Radiator.Podcasts.License do
Expand Down
1 change: 1 addition & 0 deletions lib/radiator_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ defmodule RadiatorWeb do
# Import DaisyUI components
use DaisyUIComponents, core_components: true

import RadiatorWeb.FormComponents
# Common modules used in templates
alias Phoenix.LiveView.JS
alias RadiatorWeb.Layouts
Expand Down
57 changes: 57 additions & 0 deletions lib/radiator_web/components/form_components.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
defmodule RadiatorWeb.FormComponents do
@moduledoc """
Application-specific form components.
"""
use Phoenix.Component
use Gettext, backend: RadiatorWeb.Gettext

import DaisyUIComponents.Button

@doc """
Renders a pair of form action buttons: a cancel link and a primary submit
button, placed next to each other on the right.

## Examples

<:actions>
<.form_actions cancel_to={~p"/admin/podcasts"} />
</:actions>

<:actions>
<.form_actions cancel_to={~p"/admin/podcasts"} submit_label={gettext("Create")} />
</:actions>
"""
attr :cancel_to, :string, required: true, doc: "the path to navigate to on cancel"
attr :submit_label, :string, doc: "label for the submit button"

def form_actions(assigns) do
assigns = assign_new(assigns, :submit_label, fn -> gettext("Save") end)

~H"""
<.button navigate={@cancel_to} ghost type="button">{gettext("Cancel")}</.button>
<.button variant="primary" type="submit">{@submit_label}</.button>
"""
end

@doc """
Renders a labeled detail entry for use inside a `<dl>` grid. Empty values
(`nil`, `""`, `[]`) are shown as an em-dash.

## Examples

<dl class="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-3">
<.detail label={gettext("Author")} value={@podcast.author} />
</dl>
"""
attr :label, :string, required: true
attr :value, :any, required: true

def detail(assigns) do
~H"""
<div>
<dt class="text-xs font-semibold uppercase opacity-60">{@label}</dt>
<dd>{if @value in [nil, "", []], do: "—", else: @value}</dd>
</div>
"""
end
end
131 changes: 112 additions & 19 deletions lib/radiator_web/components/layouts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,51 +27,144 @@ defmodule RadiatorWeb.Layouts do
"""
attr :flash, :map, required: true, doc: "the map of flash messages"

attr :current_user, :any, default: nil, doc: "the currently signed-in user"

attr :sidebar_podcasts, :list,
default: [],
doc: "podcasts (with episodes loaded) for the sidebar navigation"

attr :current_scope, :map,
default: nil,
doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)"

slot :inner_block, required: true

slot :breadcrumb, doc: "breadcrumb trail rendered as a full-width navigation bar" do
attr :path, :string
end

def app(assigns) do
~H"""
<header class="navbar px-4 sm:px-6 lg:px-8">
<header class="navbar px-4 sm:px-6 lg:px-8 border-b border-base-300">
<div class="flex-1">
<a href="/" class="flex-1 flex w-fit items-center gap-2">
<img src={~p"/images/logo.svg"} width="36" />
<span class="text-sm font-semibold">v{Application.spec(:phoenix, :vsn)}</span>
</a>
<a href="/" class="flex w-fit items-center gap-2 font-semibold">Radiator</a>
</div>
<div class="flex-none">
<ul class="flex flex-column px-1 space-x-4 items-center">
<li>
<a href="https://phoenixframework.org/" class="btn btn-ghost">Website</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix" class="btn btn-ghost">GitHub</a>
<ul class="flex flex-row items-center gap-2">
<li id="notifications" aria-label={gettext("Notifications")}>
<button type="button" class="btn btn-ghost btn-circle">
<.icon name="hero-bell" class="size-5" />
</button>
</li>
<li>
<.theme_toggle />
</li>
<li>
<a href="https://hexdocs.pm/phoenix/overview.html" class="btn btn-primary">
Get Started <span aria-hidden="true">&rarr;</span>
</a>
<li :if={@current_user} class="text-sm opacity-70 px-2">
{@current_user.email}
</li>
<li :if={@current_user}>
<.link href={~p"/sign-out"} class="btn btn-ghost btn-sm">
{gettext("Sign out")}
</.link>
</li>
</ul>
</div>
</header>

<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl space-y-4">
{render_slot(@inner_block)}
<div class="flex">
<.nav_sidebar
:if={@current_user}
podcasts={@sidebar_podcasts}
active_podcast_id={@active_podcast_id}
/>

<div class="flex-1 min-w-0">
<nav
:if={@breadcrumb != []}
class="breadcrumbs bg-base-300 text-sm px-4 sm:px-6 lg:px-8 py-2"
aria-label={gettext("Breadcrumb")}
>
<ul>
<li :for={crumb <- @breadcrumb}>
<.link :if={crumb[:path]} navigate={crumb[:path]}>{render_slot(crumb)}</.link>
<span :if={is_nil(crumb[:path])}>{render_slot(crumb)}</span>
</li>
</ul>
</nav>

<main class="px-4 py-10 sm:px-6 lg:px-8">
<div class="w-full space-y-4">
{render_slot(@inner_block)}
</div>
</main>
</div>
</main>
</div>

<.flash_group flash={@flash} />
"""
end

@doc """
Renders the left-hand navigation sidebar listing podcasts and their
episodes as expandable submenus.
"""
attr :podcasts, :list, required: true
attr :active_podcast_id, :string, default: nil

def nav_sidebar(assigns) do
~H"""
<aside class="w-64 shrink-0 border-r border-base-300 min-h-[calc(100vh-4rem)]">
<nav class="p-4">
<div class="flex items-center justify-between mb-2">
<h2 class="text-xs font-semibold uppercase opacity-60">
{gettext("Podcasts")}
</h2>
<.link
navigate={~p"/admin/podcasts/new"}
class="btn btn-ghost btn-xs btn-circle"
aria-label={gettext("New Podcast")}
>
<.icon name="hero-plus" class="size-4" />
</.link>
</div>

<ul class="menu menu-sm w-full p-0">
<li :for={podcast <- @podcasts}>
<details open={podcast.id == @active_podcast_id}>
<summary>
<.link navigate={~p"/admin/podcasts/#{podcast}"} class="flex-1 truncate">
{podcast.title}
</.link>
</summary>
<ul>
<li>
<.link
navigate={~p"/admin/podcasts/#{podcast}/episodes/new"}
class="text-xs opacity-70"
>
<.icon name="hero-plus" class="size-3" /> {gettext("New Episode")}
</.link>
</li>
<li :if={podcast.episodes == []} class="opacity-50">
<span class="text-xs">{gettext("No episodes")}</span>
</li>
<li :for={episode <- podcast.episodes}>
<.link navigate={~p"/admin/podcasts/#{podcast}/episodes/#{episode}"}>
<span class="truncate">
<span :if={episode.number} class="opacity-60">#{episode.number}</span>
{episode.title}
</span>
</.link>
</li>
</ul>
</details>
</li>
</ul>
</nav>
</aside>
"""
end

# @doc """
# Shows the flash group with standard titles and content.

Expand Down
3 changes: 3 additions & 0 deletions lib/radiator_web/live/admin/episodes/form_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule RadiatorWeb.Admin.Episodes.FormLive do
socket
|> assign(:form, to_form(form))
|> assign(:podcast, podcast)
|> assign(:cancel_path, ~p"/admin/podcasts/#{podcast}/episodes/#{episode}")
|> assign(:page_title, "Edit Episode")
|> assign_candicates()
|> ok()
Expand All @@ -40,6 +41,7 @@ defmodule RadiatorWeb.Admin.Episodes.FormLive do
socket
|> assign(:form, to_form(form))
|> assign(:podcast, podcast)
|> assign(:cancel_path, ~p"/admin/podcasts/#{podcast}/episodes")
|> assign(:page_title, "New Episode")
|> assign_candicates()
|> ok()
Expand Down Expand Up @@ -162,6 +164,7 @@ defmodule RadiatorWeb.Admin.Episodes.FormLive do
reject_handles =
socket.assigns.form
|> Form.value(:participants)
|> List.wrap()
|> Enum.map(&Form.value(&1, :handle))

candicates =
Expand Down
2 changes: 1 addition & 1 deletion lib/radiator_web/live/admin/episodes/form_live.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
</fieldset>

<:actions>
<.button variant="primary">{gettext("Save")}</.button>
<.form_actions cancel_to={@cancel_path} />
</:actions>
</.simple_form>
</Layouts.app>
5 changes: 4 additions & 1 deletion lib/radiator_web/live/admin/episodes/index_live.html.heex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<Layouts.app {assigns}>
<:breadcrumb path={~p"/admin/podcasts"}>{gettext("Podcasts")}</:breadcrumb>
<:breadcrumb path={~p"/admin/podcasts/#{@podcast}"}>{@podcast.title}</:breadcrumb>
<:breadcrumb>{gettext("Episodes")}</:breadcrumb>
<h1>{gettext("Episodes for %{title}", title: @podcast.title)}</h1>
<.button navigate={~p"/admin/podcasts/#{@podcast}/episodes/new"} variant="primary">
{gettext("New")}
Expand All @@ -8,7 +11,7 @@
<:col :let={episode} label={gettext("Title")}>{episode.title}</:col>
<:col :let={episode} label={gettext("Actions")}>
<.button navigate={~p"/admin/podcasts/#{@podcast}/episodes/#{episode}"}>
{gettext("Podcast")}
{gettext("Show")}
</.button>
<.button navigate={~p"/admin/podcasts/#{@podcast}/episodes/#{episode}/edit"}>
{gettext("Edit")}
Expand Down
Loading
Loading