First UI Pass (#15)

* Added scratchpad dir

* Installed Alpine

* [WIP] began integrating Tailwind and accompanying theme

* [WIP] Set up basic views for sources

* Adds new UI to media profiles page

* Removes unneeded view
This commit is contained in:
Kieran
2024-02-08 19:03:11 -08:00
committed by GitHub
parent 9e4fbfa35d
commit e1565ad22f
76 changed files with 1029 additions and 297 deletions
+2 -1
View File
@@ -27,7 +27,8 @@ defmodule Pinchflat.Profiles.MediaProfile do
schema "media_profiles" do
field :name, :string
field :output_path_template, :string
field :output_path_template, :string, default: "/{{ uploader }}/{{ title }}.{{ ext }}"
field :download_subs, :boolean, default: true
field :download_auto_subs, :boolean, default: true
+3 -1
View File
@@ -84,8 +84,10 @@ defmodule PinchflatWeb do
# HTML escaping functionality
import Phoenix.HTML
# Core UI components and translation
import PinchflatWeb.CoreComponents
import PinchflatWeb.Gettext
import PinchflatWeb.CoreComponents
import PinchflatWeb.CustomComponents.TableComponents
import PinchflatWeb.CustomComponents.ButtonComponents
# Shortcut for generating JS commands
alias Phoenix.LiveView.JS
+118 -94
View File
@@ -201,46 +201,14 @@ defmodule PinchflatWeb.CoreComponents do
def simple_form(assigns) do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<div class="mt-10 space-y-8 bg-white">
<%= render_slot(@inner_block, f) %>
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>
</div>
<%= render_slot(@inner_block, f) %>
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>
</div>
</.form>
"""
end
@doc """
Renders a button.
## Examples
<.button>Send!</.button>
<.button phx-click="go" class="ml-2">Send!</.button>
"""
attr :type, :string, default: nil
attr :class, :string, default: nil
attr :rest, :global, include: ~w(disabled form name value)
slot :inner_block, required: true
def button(assigns) do
~H"""
<button
type={@type}
class={[
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3",
"text-sm font-semibold leading-6 text-white active:text-white/80",
@class
]}
{@rest}
>
<%= render_slot(@inner_block) %>
</button>
"""
end
@doc """
Renders an input with label and error messages.
@@ -270,11 +238,12 @@ defmodule PinchflatWeb.CoreComponents do
attr :name, :any
attr :label, :string, default: nil
attr :value, :any
attr :help, :string, default: nil
attr :type, :string,
default: "text",
values: ~w(checkbox color date datetime-local email file hidden month number password
range radio search select tel text textarea time url week)
toggle range radio search select tel text textarea time url week)
attr :field, Phoenix.HTML.FormField,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
@@ -308,7 +277,7 @@ defmodule PinchflatWeb.CoreComponents do
~H"""
<div phx-feedback-for={@name}>
<label class="flex items-center gap-4 text-sm leading-6 text-zinc-600">
<label class="flex items-center gap-4 text-sm leading-6">
<input type="hidden" name={@name} value="false" />
<input
type="checkbox"
@@ -316,16 +285,54 @@ defmodule PinchflatWeb.CoreComponents do
name={@name}
value="true"
checked={@checked}
class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
class="rounded focus:ring-0"
{@rest}
/>
<%= @label %>
</label>
<.help :if={@help}><%= @help %></.help>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
end
def input(%{type: "toggle"} = assigns) do
assigns =
assign_new(assigns, :checked, fn ->
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
end)
~H"""
<div x-data={"{ enabled: #{@checked}}"}>
<.label for={@id}><%= @label %></.label>
<div class="relative">
<input type="hidden" name={@name} value="false" />
<input
type="checkbox"
id={@id}
name={@name}
value="true"
x-bind:checked="enabled"
class="sr-only"
@change="enabled = !enabled"
{@rest}
/>
<div class="inline-block cursor-pointer" @click="enabled = !enabled">
<div x-bind:class="enabled && '!bg-primary'" class="block h-8 w-14 rounded-full bg-black">
</div>
<div
x-bind:class="enabled && '!right-1 !translate-x-full'"
class="absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-white transition"
>
</div>
</div>
<.help :if={@help}><%= @help %></.help>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
</div>
"""
end
def input(%{type: "select"} = assigns) do
~H"""
<div phx-feedback-for={@name}>
@@ -333,13 +340,17 @@ defmodule PinchflatWeb.CoreComponents do
<select
id={@id}
name={@name}
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
class={[
"relative z-20 w-full appearance-none rounded border border-stroke bg-transparent py-3 pl-5 pr-12 outline-none transition",
"focus:border-primary active:border-primary dark:border-form-strokedark dark:bg-form-input text-black dark:text-white"
]}
multiple={@multiple}
{@rest}
>
<option :if={@prompt} value=""><%= @prompt %></option>
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %>
</select>
<.help :if={@help}><%= @help %></.help>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
@@ -360,6 +371,7 @@ defmodule PinchflatWeb.CoreComponents do
]}
{@rest}
><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
<.help :if={@help}><%= @help %></.help>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
@@ -376,18 +388,32 @@ defmodule PinchflatWeb.CoreComponents do
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={[
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6",
"phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400",
@errors == [] && "border-zinc-300 focus:border-zinc-400",
"w-full rounded-lg border-[1.5px] border-stroke bg-transparent px-5 py-3 font-normal text-black",
"outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter",
"dark:border-form-strokedark dark:bg-form-input dark:text-white dark:focus:border-primary",
@errors != [] && "border-rose-400 focus:border-rose-400"
]}
{@rest}
/>
<.help :if={@help}><%= @help %></.help>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
end
@doc """
Renders help text.
"""
slot :inner_block, required: true
def help(assigns) do
~H"""
<p class="mt-1 text-sm leading-5">
<%= render_slot(@inner_block) %>
</p>
"""
end
@doc """
Renders a label.
"""
@@ -396,7 +422,7 @@ defmodule PinchflatWeb.CoreComponents do
def label(assigns) do
~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
<label for={@for} class="mt-5 mb-2 block text-md font-medium text-black dark:text-white">
<%= render_slot(@inner_block) %>
</label>
"""
@@ -409,7 +435,7 @@ defmodule PinchflatWeb.CoreComponents do
def error(assigns) do
~H"""
<p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600 phx-no-feedback:hidden">
<p class="mt-1 mb-5 flex gap-3 text-md leading-6 text-rose-600 phx-no-feedback:hidden">
<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
<%= render_slot(@inner_block) %>
</p>
@@ -425,7 +451,7 @@ defmodule PinchflatWeb.CoreComponents do
slot :subtitle
slot :actions
def header(assigns) do
def old_header(assigns) do
~H"""
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
<div>
@@ -446,10 +472,10 @@ defmodule PinchflatWeb.CoreComponents do
## Examples
<.table id="users" rows={@users}>
<.old_table id="users" rows={@users}>
<:col :let={user} label="id"><%= user.id %></:col>
<:col :let={user} label="username"><%= user.username %></:col>
</.table>
</.old_table>
"""
attr :id, :string, required: true
attr :rows, :list, required: true
@@ -466,56 +492,54 @@ defmodule PinchflatWeb.CoreComponents do
slot :action, doc: "the slot for showing user actions in the last table column"
def table(assigns) do
def old_table(assigns) do
assigns =
with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
end
~H"""
<div class="overflow-y-auto px-4 sm:overflow-visible sm:px-0">
<table class="w-[40rem] mt-11 sm:w-full">
<thead class="text-sm text-left leading-6 text-zinc-500">
<tr>
<th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal"><%= col[:label] %></th>
<th :if={@action != []} class="relative p-0 pb-4">
<span class="sr-only"><%= gettext("Actions") %></span>
</th>
</tr>
</thead>
<tbody
id={@id}
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
>
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class="group hover:bg-zinc-50">
<td
:for={{col, i} <- Enum.with_index(@col)}
phx-click={@row_click && @row_click.(row)}
class={["relative p-0", @row_click && "hover:cursor-pointer"]}
>
<div class="block py-4 pr-6">
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
<%= render_slot(col, @row_item.(row)) %>
</span>
</div>
</td>
<td :if={@action != []} class="relative w-14 p-0">
<div class="relative whitespace-nowrap py-4 text-right text-sm font-medium">
<span class="absolute -inset-y-px -right-4 left-0 group-hover:bg-zinc-50 sm:rounded-r-xl" />
<span
:for={action <- @action}
class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
>
<%= render_slot(action, @row_item.(row)) %>
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<table class="w-[40rem] mt-11 sm:w-full">
<thead class="text-sm text-left leading-6 text-zinc-500">
<tr>
<th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal"><%= col[:label] %></th>
<th :if={@action != []} class="relative p-0 pb-4">
<span class="sr-only"><%= gettext("Actions") %></span>
</th>
</tr>
</thead>
<tbody
id={@id}
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
>
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class="group hover:bg-zinc-50">
<td
:for={{col, i} <- Enum.with_index(@col)}
phx-click={@row_click && @row_click.(row)}
class={["relative p-0", @row_click && "hover:cursor-pointer"]}
>
<div class="block py-4 pr-6">
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
<%= render_slot(col, @row_item.(row)) %>
</span>
</div>
</td>
<td :if={@action != []} class="relative w-14 p-0">
<div class="relative whitespace-nowrap py-4 text-right text-sm font-medium">
<span class="absolute -inset-y-px -right-4 left-0 group-hover:bg-zinc-50 sm:rounded-r-xl" />
<span
:for={action <- @action}
class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
>
<%= render_slot(action, @row_item.(row)) %>
</span>
</div>
</td>
</tr>
</tbody>
</table>
"""
end
@@ -536,10 +560,10 @@ defmodule PinchflatWeb.CoreComponents do
def list(assigns) do
~H"""
<div class="mt-2 mb-14">
<dl class="-my-4 divide-y divide-zinc-100">
<dl class="-my-4 divide-y dark:divide-strokedark">
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
<dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt>
<dd class="text-zinc-700"><%= render_slot(item) %></dd>
<dt class="w-1/4 flex-none dark:text-white"><%= item.title %></dt>
<dd class="dark:text-white"><%= render_slot(item) %></dd>
</div>
</dl>
</div>
@@ -0,0 +1,33 @@
defmodule PinchflatWeb.CustomComponents.ButtonComponents do
@moduledoc false
use Phoenix.Component
@doc """
Render a button
## Examples
<.button color="bg-primary" rounding="rounded-sm">
<span>Click me</span>
</.button>
"""
attr :color, :string, default: "bg-primary"
attr :rounding, :string, default: "rounded-sm"
attr :class, :string, default: ""
slot :inner_block, required: true
def button(assigns) do
~H"""
<button class={[
"text-center font-medium text-white",
"#{@rounding} inline-flex items-center justify-center px-10 py-4",
"#{@color}",
"hover:bg-opacity-90 lg:px-8 xl:px-10",
@class
]}>
<%= render_slot(@inner_block) %>
</button>
"""
end
end
@@ -0,0 +1,53 @@
defmodule PinchflatWeb.CustomComponents.TableComponents do
@moduledoc false
use Phoenix.Component
@doc """
Renders a table component with the given rows and columns.
## Examples
<.table rows={@users}>
<:col :let={user} label="Name"><%= user.name %></:col>
</.table>
"""
attr :rows, :list, required: true
attr :table_class, :string, default: ""
attr :row_item, :any,
default: &Function.identity/1,
doc: "the function for mapping each row before calling the :col and :action slots"
slot :col, required: true do
attr :label, :string
attr :class, :string
end
def table(assigns) do
~H"""
<table class={["w-full table-auto", @table_class]}>
<thead>
<tr class="bg-gray-2 text-left dark:bg-meta-4">
<th :for={col <- @col} class="px-4 py-4 font-medium text-black dark:text-white xl:pl-11">
<%= col[:label] %>
</th>
</tr>
</thead>
<tbody>
<tr :for={{row, i} <- Enum.with_index(@rows)}>
<td
:for={col <- @col}
class={[
"px-4 py-5 pl-9 dark:border-strokedark xl:pl-11",
i + 1 > length(@rows) && "border-b border-[#eee] dark:border-π",
col[:class]
]}
>
<%= render_slot(col, @row_item.(row)) %>
</td>
</tr>
</tbody>
</table>
"""
end
end
+24
View File
@@ -2,4 +2,28 @@ defmodule PinchflatWeb.Layouts do
use PinchflatWeb, :html
embed_templates "layouts/*"
embed_templates "layouts/partials/*"
attr :icon, :string, required: true
attr :text, :string, required: true
attr :navigate, :any, required: true
def sidebar_item(assigns) do
# I'm testing out grouping classes here. Tentative order: font, layout, color, animation, state-modifiers
~H"""
<li>
<.link
navigate={@navigate}
class={[
"font-medium text-bodydark1",
"group relative flex items-center gap-2.5 rounded-sm px-4 py-2 duration-300 ease-in-out",
"duration-300 ease-in-out",
"hover:bg-graydark dark:hover:bg-meta-4"
]}
>
<.icon name={@icon} /> <%= @text %>
</.link>
</li>
"""
end
end
@@ -1,32 +1,13 @@
<header class="px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
<div class="flex items-center gap-4">
<a href="/">
<img src={~p"/images/logo.svg"} width="36" />
</a>
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
v<%= Application.spec(:phoenix, :vsn) %>
</p>
</div>
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
<a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
@elixirphoenix
</a>
<a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700">
GitHub
</a>
<a
href="https://hexdocs.pm/phoenix/overview.html"
class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80"
>
Get Started <span aria-hidden="true">&rarr;</span>
</a>
</div>
<div class="flex h-screen overflow-hidden">
<.sidebar />
<div class="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
<.header />
<main>
<div class="mx-auto max-w-screen-2xl p-4 md:p-6 2xl:p-10">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
</main>
</div>
</header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
</main>
</div>
@@ -0,0 +1,29 @@
<header class="sticky top-0 z-999 flex w-full bg-white drop-shadow-1 dark:bg-boxdark dark:drop-shadow-none">
<div class="flex flex-grow items-center justify-end px-4 py-4 shadow-2 md:px-6 2xl:px-11">
<div class="flex items-center gap-2 sm:gap-4 lg:hidden">
<button
class="z-99999 block rounded-sm border border-stroke bg-white p-1.5 shadow-sm dark:border-strokedark dark:bg-boxdark lg:hidden"
@click.stop="sidebarToggle = !sidebarToggle"
>
<.icon name="hero-bars-3" />
</button>
<a class="block flex-shrink-0 lg:hidden" href="#">
<h2 class="text-title-md2 font-bold text-white">Pinchflat</h2>
</a>
</div>
<div class="hidden sm:block bg-meta-4 rounded-md">
<%!-- Aspirational (for now) --%>
<div class="relative">
<span class="absolute left-2 top-1/2 -translate-y-1/2 flex">
<.icon name="hero-magnifying-glass" />
</span>
<input
type="text"
placeholder="Type to search..."
class="w-full bg-transparent pl-9 pr-4 border-0 focus:ring-0 focus:outline-none xl:w-125"
/>
</div>
</div>
</div>
</header>
@@ -0,0 +1,29 @@
<aside
x-bind:class="sidebarToggle ? 'translate-x-0' : '-translate-x-full'"
class="absolute left-0 top-0 z-9999 flex h-screen w-72.5 flex-col overflow-y-hidden bg-black duration-300 ease-linear dark:bg-boxdark lg:static lg:translate-x-0"
@click.outside="sidebarToggle = false"
>
<div class="flex items-center justify-between gap-2 px-6 py-5.5 lg:py-6.5">
<h2 class="text-title-md2 font-bold text-white">Pinchflat</h2>
<button class="block lg:hidden" @click.stop="sidebarToggle = !sidebarToggle">
<.icon name="hero-arrow-left" class="fill-current" />
</button>
</div>
<div class="no-scrollbar flex flex-col overflow-y-auto duration-300 ease-linear">
<nav class="mt-5 px-4 py-4 lg:mt-9 lg:px-6">
<div>
<h3 class="mb-4 ml-4 text-sm font-medium text-bodydark2">MENU</h3>
<ul class="mb-6 flex flex-col gap-1.5">
<.sidebar_item icon="hero-tv" text="Sources" navigate={~p"/sources"} />
<.sidebar_item
icon="hero-adjustments-vertical"
text="Media Profiles"
navigate={~p"/media_profiles"}
/>
</ul>
</div>
</nav>
</div>
</aside>
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" class="[scrollbar-gutter:stable]">
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -11,7 +11,7 @@
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
</head>
<body class="bg-white antialiased">
<body x-data="{ 'sidebarToggle': false }" class="dark text-bodydark bg-boxdark-2">
<%= @inner_content %>
</body>
</html>
@@ -1,8 +1,14 @@
<.header>
Edit Media profile <%= @media_profile.id %>
<:subtitle>Use this form to manage media_profile records in your database.</:subtitle>
</.header>
<div class="mb-6 flex gap-3 flex-row items-center">
<.link navigate={~p"/media_profiles"}>
<.icon name="hero-arrow-left" class="w-10 h-10 hover:dark:text-white" />
</.link>
<h2 class="text-title-md2 font-bold text-black dark:text-white ml-4">Edit Media Profile</h2>
</div>
<.media_profile_form changeset={@changeset} action={~p"/media_profiles/#{@media_profile}"} />
<.back navigate={~p"/media_profiles"}>Back to media_profiles</.back>
<div class="rounded-sm border border-stroke bg-white px-5 pb-2.5 pt-6 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5 xl:pb-1">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10">
<.media_profile_form changeset={@changeset} action={~p"/media_profiles/#{@media_profile}"} />
</div>
</div>
</div>
@@ -1,30 +1,39 @@
<.header>
Listing Media profiles
<:actions>
<.link href={~p"/media_profiles/new"}>
<.button>New Media profile</.button>
<div class="mb-6 flex gap-3 flex-row items-center justify-between">
<h2 class="text-title-md2 font-bold text-black dark:text-white">All Media Profiles</h2>
<nav>
<.link navigate={~p"/media_profiles/new"}>
<.button color="bg-primary" rounding="rounded-full">
<span class="font-bold mx-2">+</span> New Media Profile
</.button>
</.link>
</:actions>
</.header>
</nav>
</div>
<.table
id="media_profiles"
rows={@media_profiles}
row_click={&JS.navigate(~p"/media_profiles/#{&1}")}
>
<:col :let={media_profile} label="Name"><%= media_profile.name %></:col>
<:col :let={media_profile} label="Output path template">
<%= media_profile.output_path_template %>
</:col>
<:action :let={media_profile}>
<div class="sr-only">
<.link navigate={~p"/media_profiles/#{media_profile}"}>Show</.link>
<div class="rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10">
<.table rows={@media_profiles} table_class="text-black dark:text-white">
<:col :let={media_profile} label="Name">
<%= media_profile.name %>
</:col>
<:col :let={media_profile} label="Output Template">
<code class="text-sm"><%= media_profile.output_path_template %></code>
</:col>
<:col :let={media_profile} label="" class="flex place-content-evenly">
<.link
navigate={~p"/media_profiles/#{media_profile.id}"}
class="hover:text-secondary duration-200 ease-in-out mx-0.5"
>
<.icon name="hero-eye" />
</.link>
<.link
navigate={~p"/media_profiles/#{media_profile.id}/edit"}
class="hover:text-secondary duration-200 ease-in-out mx-0.5"
>
<.icon name="hero-pencil-square" />
</.link>
</:col>
</.table>
</div>
<.link navigate={~p"/media_profiles/#{media_profile}/edit"}>Edit</.link>
</:action>
<:action :let={media_profile}>
<.link href={~p"/media_profiles/#{media_profile}"} method="delete" data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>
</div>
</div>
@@ -2,29 +2,62 @@
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:name]} type="text" label="Name" />
<.input field={f[:output_path_template]} type="text" label="Output path template" />
<h3 class="my-4 text-2xl text-black dark:text-white">
General Options
</h3>
<.input field={f[:name]} type="text" label="Name" placeholder="New Profile" help="(required)" />
<.input
field={f[:output_path_template]}
type="text"
label="Output path template"
help="TODO: provide docs (required)"
/>
<h3>Subtitle Options</h3>
<.input field={f[:download_subs]} type="checkbox" label="Download Subs" />
<.input field={f[:download_auto_subs]} type="checkbox" label="Download Autogenerated Subs" />
<.input field={f[:embed_subs]} type="checkbox" label="Embed Subs" />
<.input field={f[:sub_langs]} type="text" label="Sub Langs" />
<h3 class="mb-4 mt-8 text-2xl text-black dark:text-white">
Subtitle Options
</h3>
<.input field={f[:download_subs]} type="toggle" label="Download Subtitles" />
<.input
field={f[:download_auto_subs]}
type="toggle"
label="Download Autogenerated Subtitles"
help="Prefers normal subs but will download autogenerated if needed"
/>
<.input
field={f[:embed_subs]}
type="toggle"
label="Embed Subtitles"
help="Embeds subtitles in the video file itself, if supported"
/>
<.input
field={f[:sub_langs]}
type="text"
label="Subtitle Languages"
help="Use commas for multiple languages (eg: en,de)"
/>
<h3>Thumbnail Options</h3>
<.input field={f[:download_thumbnail]} type="checkbox" label="Download Thumbnail" />
<.input field={f[:embed_thumbnail]} type="checkbox" label="Embed Thumbnail" />
<h3 class="mb-4 mt-8 text-2xl text-black dark:text-white">
Thumbnail Options
</h3>
<.input field={f[:download_thumbnail]} type="toggle" label="Download Thumbnail" />
<.input field={f[:embed_thumbnail]} type="toggle" label="Embed Thumbnail" />
<h3>Metadata Options</h3>
<.input field={f[:download_metadata]} type="checkbox" label="Download Metadata" />
<.input field={f[:embed_metadata]} type="checkbox" label="Embed Metadata" />
<h3 class="mb-4 mt-8 text-2xl text-black dark:text-white">
Metadata Options
</h3>
<.input field={f[:download_metadata]} type="toggle" label="Download Metadata" />
<.input field={f[:embed_metadata]} type="toggle" label="Embed Metadata" />
<h3 class="mb-4 mt-8 text-2xl text-black dark:text-white">
Release Format Options
</h3>
<h3>Release Format Options</h3>
<.input
field={f[:shorts_behaviour]}
options={friendly_format_type_options()}
type="select"
label="Include Shorts?"
help="Experimental"
/>
<.input
field={f[:livestream_behaviour]}
@@ -34,6 +67,6 @@
/>
<:actions>
<.button>Save Media profile</.button>
<.button class="mt-15 mb-5 sm:mb-7.5">Save Media profile</.button>
</:actions>
</.simple_form>
@@ -1,8 +1,14 @@
<.header>
New Media profile
<:subtitle>Use this form to manage media_profile records in your database.</:subtitle>
</.header>
<div class="mb-6 flex gap-3 flex-row items-center">
<.link navigate={~p"/media_profiles"}>
<.icon name="hero-arrow-left" class="w-10 h-10 hover:dark:text-white" />
</.link>
<h2 class="text-title-md2 font-bold text-black dark:text-white ml-4">New Media Profile</h2>
</div>
<.media_profile_form changeset={@changeset} action={~p"/media_profiles"} />
<.back navigate={~p"/media_profiles"}>Back to media_profiles</.back>
<div class="rounded-sm border border-stroke bg-white px-5 pb-2.5 pt-6 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5 xl:pb-1">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10">
<.media_profile_form changeset={@changeset} action={~p"/media_profiles"} />
</div>
</div>
</div>
@@ -1,13 +1,26 @@
<.header>
Media profile <%= @media_profile.id %>
<:subtitle>This is a media_profile record from your database.</:subtitle>
<:actions>
<.link href={~p"/media_profiles/#{@media_profile}/edit"}>
<.button>Edit media_profile</.button>
<div class="mb-6 flex gap-3 flex-row items-center justify-between">
<div class="flex gap-3 items-center">
<.link navigate={~p"/media_profiles"}>
<.icon name="hero-arrow-left" class="w-10 h-10 hover:dark:text-white" />
</.link>
</:actions>
</.header>
<h2 class="text-title-md2 font-bold text-black dark:text-white ml-4">
Media Profile #<%= @media_profile.id %>
</h2>
</div>
<.list_items_from_map map={Map.from_struct(@media_profile)} />
<.back navigate={~p"/media_profiles"}>Back to media_profiles</.back>
<nav>
<.link navigate={~p"/media_profiles/#{@media_profile}/edit"}>
<.button color="bg-primary" rounding="rounded-full">
<.icon name="hero-pencil-square" class="mr-2" /> Edit Media Profile
</.button>
</.link>
</nav>
</div>
<div class="rounded-sm border border-stroke bg-white px-5 pb-2.5 pt-6 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5 xl:pb-1">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10 dark:text-white">
<h3 class="font-bold text-xl">Attributes</h3>
<.list_items_from_map map={Map.from_struct(@media_profile)} />
</div>
</div>
</div>
@@ -7,7 +7,7 @@ defmodule PinchflatWeb.MediaSources.SourceController do
alias Pinchflat.MediaSource.Source
def index(conn, _params) do
sources = MediaSource.list_sources()
sources = Repo.preload(MediaSource.list_sources(), :media_profile)
render(conn, :index, sources: sources)
end
@@ -23,7 +23,7 @@ defmodule PinchflatWeb.MediaSources.SourceController do
{:ok, source} ->
conn
|> put_flash(:info, "Source created successfully.")
|> redirect(to: ~p"/media_sources/sources/#{source}")
|> redirect(to: ~p"/sources/#{source}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset, media_profiles: media_profiles())
@@ -53,7 +53,7 @@ defmodule PinchflatWeb.MediaSources.SourceController do
{:ok, source} ->
conn
|> put_flash(:info, "Source updated successfully.")
|> redirect(to: ~p"/media_sources/sources/#{source}")
|> redirect(to: ~p"/sources/#{source}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit,
@@ -70,7 +70,7 @@ defmodule PinchflatWeb.MediaSources.SourceController do
conn
|> put_flash(:info, "Source deleted successfully.")
|> redirect(to: ~p"/media_sources/sources")
|> redirect(to: ~p"/sources")
end
defp media_profiles do
@@ -1,12 +1,18 @@
<.header>
Edit Source <%= @source.id %>
<:subtitle>Use this form to manage source records in your database.</:subtitle>
</.header>
<div class="mb-6 flex gap-3 flex-row items-center">
<.link navigate={~p"/sources"}>
<.icon name="hero-arrow-left" class="w-10 h-10 hover:dark:text-white" />
</.link>
<h2 class="text-title-md2 font-bold text-black dark:text-white ml-4">Edit Source</h2>
</div>
<.source_form
changeset={@changeset}
media_profiles={@media_profiles}
action={~p"/media_sources/sources/#{@source}"}
/>
<.back navigate={~p"/media_sources/sources"}>Back to sources</.back>
<div class="rounded-sm border border-stroke bg-white px-5 pb-2.5 pt-6 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5 xl:pb-1">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10">
<.source_form
changeset={@changeset}
media_profiles={@media_profiles}
action={~p"/sources/#{@source}"}
/>
</div>
</div>
</div>
@@ -1,24 +1,48 @@
<.header>
Listing Sources
<:actions>
<.link href={~p"/media_sources/sources/new"}>
<.button>New Source</.button>
<div class="mb-6 flex gap-3 flex-row items-center justify-between">
<h2 class="text-title-md2 font-bold text-black dark:text-white">All Sources</h2>
<nav>
<.link navigate={~p"/sources/new"}>
<.button color="bg-primary" rounding="rounded-full">
<span class="font-bold mx-2">+</span> New Source
</.button>
</.link>
</:actions>
</.header>
</nav>
</div>
<.table id="sources" rows={@sources} row_click={&JS.navigate(~p"/media_sources/sources/#{&1}")}>
<:col :let={source} label="Collection Name"><%= source.collection_name %></:col>
<:col :let={source} label="Collection ID"><%= source.collection_id %></:col>
<:action :let={source}>
<div class="sr-only">
<.link navigate={~p"/media_sources/sources/#{source}"}>Show</.link>
<div class="rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10">
<.table rows={@sources} table_class="text-black dark:text-white">
<:col :let={source} label="Name">
<%= source.friendly_name || source.collection_name %>
</:col>
<:col :let={source} label="Type"><%= source.collection_type %></:col>
<:col :let={source} label="Should Download?">
<.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
</:col>
<:col :let={source} label="Media Profile">
<.link
navigate={~p"/media_profiles/#{source.media_profile_id}"}
class="hover:text-secondary duration-200 ease-in-out"
>
<%= source.media_profile.name %>
</.link>
</:col>
<:col :let={source} label="" class="flex place-content-evenly">
<.link
navigate={~p"/sources/#{source.id}"}
class="hover:text-secondary duration-200 ease-in-out mx-0.5"
>
<.icon name="hero-eye" />
</.link>
<.link
navigate={~p"/sources/#{source.id}/edit"}
class="hover:text-secondary duration-200 ease-in-out mx-0.5"
>
<.icon name="hero-pencil-square" />
</.link>
</:col>
</.table>
</div>
<.link navigate={~p"/media_sources/sources/#{source}/edit"}>Edit</.link>
</:action>
<:action :let={source}>
<.link href={~p"/media_sources/sources/#{source}"} method="delete" data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>
</div>
</div>
@@ -1,12 +1,14 @@
<.header>
New Source
<:subtitle>Use this form to manage source records in your database.</:subtitle>
</.header>
<div class="mb-6 flex gap-3 flex-row items-center">
<.link navigate={~p"/sources"}>
<.icon name="hero-arrow-left" class="w-10 h-10 hover:dark:text-white" />
</.link>
<h2 class="text-title-md2 font-bold text-black dark:text-white ml-4">New Source</h2>
</div>
<.source_form
changeset={@changeset}
media_profiles={@media_profiles}
action={~p"/media_sources/sources"}
/>
<.back navigate={~p"/media_sources/sources"}>Back to sources</.back>
<div class="rounded-sm border border-stroke bg-white px-5 pb-2.5 pt-6 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5 xl:pb-1">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10">
<.source_form changeset={@changeset} media_profiles={@media_profiles} action={~p"/sources"} />
</div>
</div>
</div>
@@ -1,23 +1,38 @@
<.header>
Source <%= @source.id %>
<:subtitle>This is a source record from your database.</:subtitle>
<:actions>
<.link href={~p"/media_sources/sources/#{@source}/edit"}>
<.button>Edit source</.button>
<div class="mb-6 flex gap-3 flex-row items-center justify-between">
<div class="flex gap-3 items-center">
<.link navigate={~p"/sources"}>
<.icon name="hero-arrow-left" class="w-10 h-10 hover:dark:text-white" />
</.link>
</:actions>
</.header>
<h2 class="text-title-md2 font-bold text-black dark:text-white ml-4">
Source #<%= @source.id %>
</h2>
</div>
<h3 class="mt-14">Relationships</h3>
<.list>
<:item title="media_profile">
<.link href={~p"/media_profiles/#{@source.media_profile}"}>
<%= @source.media_profile.name %>
<nav>
<.link navigate={~p"/sources/#{@source}/edit"}>
<.button color="bg-primary" rounding="rounded-full">
<.icon name="hero-pencil-square" class="mr-2" /> Edit Source
</.button>
</.link>
</:item>
</.list>
</nav>
</div>
<div class="rounded-sm border border-stroke bg-white px-5 pb-2.5 pt-6 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5 xl:pb-1">
<div class="max-w-full overflow-x-auto">
<div class="flex flex-col gap-10 dark:text-white">
<h3 class="mt-14 font-bold text-xl">Relationships</h3>
<.list>
<:item title="media_profile">
<.link
navigate={~p"/media_profiles/#{@source.media_profile_id}"}
class="hover:text-secondary duration-200 ease-in-out"
>
<%= @source.media_profile.name %>
</.link>
</:item>
</.list>
<h3>Attributes</h3>
<.list_items_from_map map={Map.from_struct(@source)} />
<.back navigate={~p"/media_sources/sources"}>Back to sources</.back>
<h3 class="font-bold text-xl">Attributes</h3>
<.list_items_from_map map={Map.from_struct(@source)} />
</div>
</div>
</div>
@@ -3,6 +3,13 @@
Oops, something went wrong! Please check the errors below.
</.error>
<.input
field={f[:original_url]}
type="text"
label="Source URL"
help="URL of a channel or playlist (required)"
/>
<.input field={f[:friendly_name]} type="text" label="Friendly Name" />
<.input
@@ -16,21 +23,25 @@
field={f[:collection_type]}
options={friendly_collection_types()}
type="select"
label="Collection Type"
label="Source Type"
/>
<.input field={f[:original_url]} type="text" label="Source URL" />
<.input
field={f[:index_frequency_minutes]}
options={friendly_index_frequencies()}
type="select"
label="Index Frequency"
help="Roughly how often to check for media to download"
/>
<.input field={f[:download_media]} type="checkbox" label="Download Media?" />
<.input
field={f[:download_media]}
type="toggle"
label="Download Media?"
help="Unchecking still indexes media but it won't be downloaded"
/>
<:actions>
<.button>Save Source</.button>
<.button class="mt-15 mb-5 sm:mb-7.5">Save Source</.button>
</:actions>
</.simple_form>
@@ -4,6 +4,6 @@ defmodule PinchflatWeb.PageController do
def home(conn, _params) do
# The home page is often custom made,
# so skip the default app layout.
render(conn, :home, layout: false)
render(conn, :home)
end
end
+1 -4
View File
@@ -20,10 +20,7 @@ defmodule PinchflatWeb.Router do
get "/", PageController, :home
resources "/media_profiles", MediaProfiles.MediaProfileController
scope "/media_sources", MediaSources do
resources "/sources", SourceController
end
resources "/sources", MediaSources.SourceController
end
# Other scopes may use custom stacks.