Basic onboading flow (#24)
* removed unused file * [WIP] Created onboarding form * Finished basic onboarding flow; added tests * Improved onboarding language
This commit is contained in:
parent
1f56db01a5
commit
69c47c2c65
27 changed files with 313 additions and 244 deletions
0
.github/workflows/list_and_test.yml
vendored
0
.github/workflows/list_and_test.yml
vendored
|
|
@ -100,6 +100,7 @@ defmodule Pinchflat.Media do
|
|||
field -> List.wrap(mapped_struct[field])
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.filter(&is_binary/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ defmodule Pinchflat.Profiles.MediaProfile do
|
|||
schema "media_profiles" do
|
||||
field :name, :string
|
||||
|
||||
field :output_path_template, :string, default: "/{{ uploader }}/{{ title }}.{{ ext }}"
|
||||
field :output_path_template, :string, default: "/{{ uploader }}/{{ title }}/{{ title }}-{{ id }}.{{ ext }}"
|
||||
|
||||
field :download_subs, :boolean, default: true
|
||||
field :download_auto_subs, :boolean, default: true
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ defmodule PinchflatWeb do
|
|||
import Plug.Conn
|
||||
import PinchflatWeb.Gettext
|
||||
|
||||
alias PinchflatWeb.Layouts
|
||||
|
||||
unquote(verified_routes())
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,18 +14,22 @@ defmodule PinchflatWeb.CustomComponents.ButtonComponents do
|
|||
attr :color, :string, default: "bg-primary"
|
||||
attr :rounding, :string, default: "rounded-sm"
|
||||
attr :class, :string, default: ""
|
||||
attr :disabled, :boolean, default: false
|
||||
|
||||
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
|
||||
]}>
|
||||
<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
|
||||
]}
|
||||
disabled={@disabled}
|
||||
>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</button>
|
||||
"""
|
||||
|
|
|
|||
18
lib/pinchflat_web/components/layouts/onboarding.html.heex
Normal file
18
lib/pinchflat_web/components/layouts/onboarding.html.heex
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<div class="flex h-screen overflow-hidden">
|
||||
<div class="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
|
||||
<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 px-4 py-4 shadow-2 md:px-6 2xl:px-11">
|
||||
<div class="flex items-center gap-2 sm:gap-4">
|
||||
<h2 class="text-title-md2 font-bold text-white">Pinchflat</h2>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
>
|
||||
<.icon name="hero-bars-3" />
|
||||
</button>
|
||||
<a class="hidden sm:block flex-shrink-0 lg:hidden" href="#">
|
||||
<a class="hidden sm:block flex-shrink-0 lg:hidden" href="/">
|
||||
<h2 class="text-title-md2 font-bold text-white">Pinchflat</h2>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
name="q"
|
||||
value={@params["q"]}
|
||||
placeholder="Type to search..."
|
||||
class="w-full bg-transparent pl-9 pr-4 border-0 focus:ring-0 focus:outline-none xl:w-125"
|
||||
class="w-full bg-transparent pl-9 pr-4 border-0 focus:ring-0 focus:outline-none lg:w-125"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
@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>
|
||||
<a href="/">
|
||||
<h2 class="text-title-md2 font-bold text-white">Pinchflat</h2>
|
||||
</a>
|
||||
|
||||
<button class="block lg:hidden" @click.stop="sidebarToggle = !sidebarToggle">
|
||||
<.icon name="hero-arrow-left" class="fill-current" />
|
||||
|
|
@ -15,6 +17,8 @@
|
|||
<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-home" text="Home" navigate={~p"/"} />
|
||||
|
||||
<.sidebar_item icon="hero-tv" text="Sources" navigate={~p"/sources"} />
|
||||
|
||||
<.sidebar_item icon="hero-adjustments-vertical" text="Media Profiles" navigate={~p"/media_profiles"} />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="csrf-token" content={get_csrf_token()} />
|
||||
<.live_title suffix=" · Phoenix Framework">
|
||||
<.live_title>
|
||||
<%= assigns[:page_title] || "Pinchflat" %>
|
||||
</.live_title>
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
|
|
|
|||
|
|
@ -11,18 +11,30 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileController do
|
|||
|
||||
def new(conn, _params) do
|
||||
changeset = Profiles.change_media_profile(%MediaProfile{})
|
||||
render(conn, :new, changeset: changeset)
|
||||
|
||||
if get_session(conn, :onboarding) do
|
||||
render(conn, :new, changeset: changeset, layout: {Layouts, :onboarding})
|
||||
else
|
||||
render(conn, :new, changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def create(conn, %{"media_profile" => media_profile_params}) do
|
||||
case Profiles.create_media_profile(media_profile_params) do
|
||||
{:ok, media_profile} ->
|
||||
redirect_location =
|
||||
if get_session(conn, :onboarding), do: ~p"/?onboarding=1", else: ~p"/media_profiles/#{media_profile}"
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Media profile created successfully.")
|
||||
|> redirect(to: ~p"/media_profiles/#{media_profile}")
|
||||
|> redirect(to: redirect_location)
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, :new, changeset: changeset)
|
||||
if get_session(conn, :onboarding) do
|
||||
render(conn, :new, changeset: changeset, layout: {Layouts, :onboarding})
|
||||
else
|
||||
render(conn, :new, changeset: changeset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
field={f[:embed_subs]}
|
||||
type="toggle"
|
||||
label="Embed Subtitles"
|
||||
help="Embeds subtitles in the video file itself, if supported"
|
||||
help="Embeds subtitles in the video file itself, if supported (recommended)"
|
||||
/>
|
||||
<.input
|
||||
field={f[:sub_langs]}
|
||||
|
|
@ -40,13 +40,23 @@
|
|||
Thumbnail Options
|
||||
</h3>
|
||||
<.input field={f[:download_thumbnail]} type="toggle" label="Download Thumbnail" />
|
||||
<.input field={f[:embed_thumbnail]} type="toggle" label="Embed Thumbnail" />
|
||||
<.input
|
||||
field={f[:embed_thumbnail]}
|
||||
type="toggle"
|
||||
label="Embed Thumbnail"
|
||||
help="Embeds thumbnail in the video file itself, if supported (recommended)"
|
||||
/>
|
||||
|
||||
<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" />
|
||||
<.input
|
||||
field={f[:embed_metadata]}
|
||||
type="toggle"
|
||||
label="Embed Metadata"
|
||||
help="Embeds metadata in the video file itself, if supported (recommended)"
|
||||
/>
|
||||
|
||||
<h3 class="mb-4 mt-8 text-2xl text-black dark:text-white">
|
||||
Release Format Options
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="mb-6 flex gap-3 flex-row items-center">
|
||||
<.link navigate={~p"/media_profiles"}>
|
||||
<.link :if={!Plug.Conn.get_session(@conn, :onboarding)} 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>
|
||||
|
|
|
|||
|
|
@ -16,18 +16,37 @@ defmodule PinchflatWeb.MediaSources.SourceController do
|
|||
def new(conn, _params) do
|
||||
changeset = MediaSource.change_source(%Source{})
|
||||
|
||||
render(conn, :new, changeset: changeset, media_profiles: media_profiles())
|
||||
if get_session(conn, :onboarding) do
|
||||
render(conn, :new,
|
||||
changeset: changeset,
|
||||
media_profiles: media_profiles(),
|
||||
layout: {Layouts, :onboarding}
|
||||
)
|
||||
else
|
||||
render(conn, :new, changeset: changeset, media_profiles: media_profiles())
|
||||
end
|
||||
end
|
||||
|
||||
def create(conn, %{"source" => source_params}) do
|
||||
case MediaSource.create_source(source_params) do
|
||||
{:ok, source} ->
|
||||
redirect_location =
|
||||
if get_session(conn, :onboarding), do: ~p"/?onboarding=1", else: ~p"/sources/#{source}"
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Source created successfully.")
|
||||
|> redirect(to: ~p"/sources/#{source}")
|
||||
|> redirect(to: redirect_location)
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, :new, changeset: changeset, media_profiles: media_profiles())
|
||||
if get_session(conn, :onboarding) do
|
||||
render(conn, :new,
|
||||
changeset: changeset,
|
||||
media_profiles: media_profiles(),
|
||||
layout: {Layouts, :onboarding}
|
||||
)
|
||||
else
|
||||
render(conn, :new, changeset: changeset, media_profiles: media_profiles())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="mb-6 flex gap-3 flex-row items-center">
|
||||
<.link navigate={~p"/sources"}>
|
||||
<.link :if={!Plug.Conn.get_session(@conn, :onboarding)} 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>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
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="Custom Name" />
|
||||
|
||||
<.input field={f[:friendly_name]} type="text" label="Friendly Name" />
|
||||
<.input field={f[:original_url]} type="text" label="Source URL" help="URL of a channel or playlist (required)" />
|
||||
|
||||
<.input
|
||||
field={f[:media_profile_id]}
|
||||
|
|
@ -21,14 +21,14 @@
|
|||
options={friendly_index_frequencies()}
|
||||
type="select"
|
||||
label="Index Frequency"
|
||||
help="Roughly how often to check for media to download"
|
||||
help="The time between one index of this source finishing and the next one starting"
|
||||
/>
|
||||
|
||||
<.input
|
||||
field={f[:download_media]}
|
||||
type="toggle"
|
||||
label="Download Media?"
|
||||
help="Unchecking still indexes media but it won't be downloaded"
|
||||
help="Unchecking still indexes media but it won't be downloaded until you enable this option"
|
||||
/>
|
||||
|
||||
<:actions>
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
defmodule PinchflatWeb.PageController do
|
||||
use PinchflatWeb, :controller
|
||||
|
||||
def home(conn, _params) do
|
||||
# The home page is often custom made,
|
||||
# so skip the default app layout.
|
||||
render(conn, :home)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
<.flash_group flash={@flash} />
|
||||
<div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]">
|
||||
<svg
|
||||
viewBox="0 0 1480 957"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
class="absolute inset-0 h-full w-full"
|
||||
preserveAspectRatio="xMinYMid slice"
|
||||
>
|
||||
<path fill="#EE7868" d="M0 0h1480v957H0z" />
|
||||
<path
|
||||
d="M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z"
|
||||
fill="#FF9F92"
|
||||
/>
|
||||
<path
|
||||
d="M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z"
|
||||
fill="#FA8372"
|
||||
/>
|
||||
<path
|
||||
d="M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z"
|
||||
fill="#E96856"
|
||||
fill-opacity=".6"
|
||||
/>
|
||||
<path
|
||||
d="M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z"
|
||||
fill="#C42652"
|
||||
fill-opacity=".2"
|
||||
/>
|
||||
<path
|
||||
d="M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z"
|
||||
fill="#A41C42"
|
||||
fill-opacity=".2"
|
||||
/>
|
||||
<path
|
||||
d="M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z"
|
||||
fill="#A41C42"
|
||||
fill-opacity=".2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32">
|
||||
<div class="mx-auto max-w-xl lg:mx-0">
|
||||
<svg viewBox="0 0 71 48" class="h-12" aria-hidden="true">
|
||||
<path
|
||||
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
|
||||
fill="#FD4F00"
|
||||
/>
|
||||
</svg>
|
||||
<h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
|
||||
Phoenix Framework
|
||||
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
|
||||
v<%= Application.spec(:phoenix, :vsn) %>
|
||||
</small>
|
||||
</h1>
|
||||
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900">
|
||||
Peace of mind from prototype to production.
|
||||
</p>
|
||||
<p class="mt-4 text-base leading-7 text-zinc-600">
|
||||
Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.
|
||||
</p>
|
||||
<div class="flex">
|
||||
<div class="w-full sm:w-auto">
|
||||
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
|
||||
<a
|
||||
href="https://hexdocs.pm/phoenix/overview.html"
|
||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
||||
>
|
||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
||||
</span>
|
||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
|
||||
<path d="m12 4 10-2v18l-10 2V4Z" fill="#18181B" fill-opacity=".15" />
|
||||
<path
|
||||
d="M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2"
|
||||
stroke="#18181B"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Guides & Docs
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/phoenixframework/phoenix"
|
||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
||||
>
|
||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
||||
</span>
|
||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z"
|
||||
fill="#18181B"
|
||||
/>
|
||||
</svg>
|
||||
Source Code
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href={"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md"}
|
||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
||||
>
|
||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
||||
</span>
|
||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
|
||||
<path
|
||||
d="M12 1v6M12 17v6"
|
||||
stroke="#18181B"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="4"
|
||||
fill="#18181B"
|
||||
fill-opacity=".15"
|
||||
stroke="#18181B"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Changelog
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-zinc-700 sm:grid-cols-2">
|
||||
<div>
|
||||
<a
|
||||
href="https://twitter.com/elixirphoenix"
|
||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
||||
>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600">
|
||||
<path d="M5.403 14c5.283 0 8.172-4.617 8.172-8.62 0-.131 0-.262-.008-.391A6.033 6.033 0 0 0 15 3.419a5.503 5.503 0 0 1-1.65.477 3.018 3.018 0 0 0 1.263-1.676 5.579 5.579 0 0 1-1.824.736 2.832 2.832 0 0 0-1.63-.916 2.746 2.746 0 0 0-1.821.319A2.973 2.973 0 0 0 8.076 3.78a3.185 3.185 0 0 0-.182 1.938 7.826 7.826 0 0 1-3.279-.918 8.253 8.253 0 0 1-2.64-2.247 3.176 3.176 0 0 0-.315 2.208 3.037 3.037 0 0 0 1.203 1.836A2.739 2.739 0 0 1 1.56 6.22v.038c0 .7.23 1.377.65 1.919.42.54 1.004.912 1.654 1.05-.423.122-.866.14-1.297.052.184.602.541 1.129 1.022 1.506a2.78 2.78 0 0 0 1.662.598 5.656 5.656 0 0 1-2.007 1.074A5.475 5.475 0 0 1 1 12.64a7.827 7.827 0 0 0 4.403 1.358" />
|
||||
</svg>
|
||||
Follow on Twitter
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://elixirforum.com"
|
||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
||||
>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600">
|
||||
<path d="M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z" />
|
||||
</svg>
|
||||
Discuss on the Elixir Forum
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://web.libera.chat/#elixir"
|
||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
||||
>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M6.356 2.007a.75.75 0 0 1 .637.849l-1.5 10.5a.75.75 0 1 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.637ZM11.356 2.008a.75.75 0 0 1 .637.848l-1.5 10.5a.75.75 0 0 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.636Z"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14 5.25a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75ZM13 10.75a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75Z"
|
||||
/>
|
||||
</svg>
|
||||
Chat on Libera IRC
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://discord.gg/elixir"
|
||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
||||
>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600">
|
||||
<path d="M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z" />
|
||||
</svg>
|
||||
Join our Discord server
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://fly.io/docs/elixir/getting-started/"
|
||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600">
|
||||
<path d="M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z" />
|
||||
</svg>
|
||||
Deploy your application
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
35
lib/pinchflat_web/controllers/pages/page_controller.ex
Normal file
35
lib/pinchflat_web/controllers/pages/page_controller.ex
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
defmodule PinchflatWeb.Pages.PageController do
|
||||
use PinchflatWeb, :controller
|
||||
|
||||
alias Pinchflat.Repo
|
||||
alias Pinchflat.MediaSource.Source
|
||||
alias Pinchflat.Profiles.MediaProfile
|
||||
|
||||
def home(conn, params) do
|
||||
force_onboarding = params["onboarding"]
|
||||
media_profiles_exist = Repo.exists?(MediaProfile)
|
||||
sources_exist = Repo.exists?(Source)
|
||||
|
||||
if !force_onboarding && media_profiles_exist && sources_exist do
|
||||
render_home_page(conn)
|
||||
else
|
||||
render_onboarding_page(conn, media_profiles_exist, sources_exist)
|
||||
end
|
||||
end
|
||||
|
||||
defp render_home_page(conn) do
|
||||
conn
|
||||
|> put_session(:onboarding, false)
|
||||
|> render(:home)
|
||||
end
|
||||
|
||||
defp render_onboarding_page(conn, media_profiles_exist, sources_exist) do
|
||||
conn
|
||||
|> put_session(:onboarding, true)
|
||||
|> render(:onboarding_checklist,
|
||||
media_profiles_exist: media_profiles_exist,
|
||||
sources_exist: sources_exist,
|
||||
layout: {Layouts, :onboarding}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
defmodule PinchflatWeb.PageHTML do
|
||||
defmodule PinchflatWeb.Pages.PageHTML do
|
||||
use PinchflatWeb, :html
|
||||
|
||||
embed_templates "page_html/*"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<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 text-center my-10">
|
||||
<h2 class="text-2xl">TODO: Put some useful info or analytics here</h2>
|
||||
<p>For now, the options in the sidebar have what you need</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<div class="rounded-sm border border-stroke bg-white px-5 py-5 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5">
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<div class="flex flex-col gap-10 items-center dark:text-white">
|
||||
<section class="text-center">
|
||||
<h2 class="text-3xl font-bold">Welcome to Pinchflat</h2>
|
||||
<p class="text-lg text-bodydark">Getting started is easy</p>
|
||||
</section>
|
||||
<section class={["text-center", @media_profiles_exist && "opacity-20"]}>
|
||||
<h3 class="text-xl font-bold">1. Create a Media Profile</h3>
|
||||
<p class="text-md text-bodydark">Media Profiles set your preferences for fetching and downloading media.</p>
|
||||
<p class="text-md text-bodydark">Don't worry, you can create more Media Profiles later!</p>
|
||||
<div class="mt-8">
|
||||
<.link navigate={~p"/media_profiles/new"}>
|
||||
<.button color="bg-primary" rounding="rounded-full" disabled={@media_profiles_exist}>
|
||||
<span class="font-bold mx-2">+</span> New Media Profile
|
||||
</.button>
|
||||
</.link>
|
||||
</div>
|
||||
</section>
|
||||
<section class={["text-center", (!@media_profiles_exist || @sources_exist) && "opacity-20"]}>
|
||||
<h3 class="text-xl font-bold">2. Create a Source</h3>
|
||||
<p class="text-md text-bodydark">Sources are the channels and playlists you want to download.</p>
|
||||
<p class="text-md text-bodydark">
|
||||
Each Media Profile can control many Sources so it's easy to add more content!
|
||||
</p>
|
||||
<div class="mt-8">
|
||||
<.link navigate={~p"/sources/new"}>
|
||||
<.button color="bg-primary" rounding="rounded-full" disabled={not @media_profiles_exist}>
|
||||
<span class="font-bold mx-2">+</span> New Source
|
||||
</.button>
|
||||
</.link>
|
||||
</div>
|
||||
</section>
|
||||
<section class={["text-center", @sources_exist || "opacity-20"]}>
|
||||
<h3 class="text-xl font-bold">3. That's it!</h3>
|
||||
<p class="text-md text-bodydark">Everything is set up and running based on your preferences.</p>
|
||||
<p class="text-md text-bodydark">
|
||||
Remember that indexing and downloading media can take a while, many hours for a large source.
|
||||
</p>
|
||||
<p class="text-md text-bodydark">Feel free to add more Media Profiles or Sources in the meantime!</p>
|
||||
<div class="mt-8">
|
||||
<.link navigate={~p"/"}>
|
||||
<.button color="bg-primary" rounding="rounded-full" disabled={not @sources_exist}>
|
||||
Let's Go <span class="font-bold mx-2">🚀</span>
|
||||
</.button>
|
||||
</.link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -17,7 +17,7 @@ defmodule PinchflatWeb.Router do
|
|||
scope "/", PinchflatWeb do
|
||||
pipe_through :browser
|
||||
|
||||
get "/", PageController, :home
|
||||
get "/", Pages.PageController, :home
|
||||
|
||||
resources "/media_profiles", MediaProfiles.MediaProfileController
|
||||
resources "/search", Searches.SearchController, only: [:show], singleton: true
|
||||
|
|
|
|||
|
|
@ -245,6 +245,18 @@ defmodule Pinchflat.MediaTest do
|
|||
"video/test.srt"
|
||||
]
|
||||
end
|
||||
|
||||
test "strips out nil values" do
|
||||
filepaths = %{
|
||||
media_filepath: "/video/test.mp4",
|
||||
thumbnail_filepath: nil,
|
||||
subtitle_filepaths: [["en", nil]]
|
||||
}
|
||||
|
||||
media_item = media_item_fixture(filepaths)
|
||||
|
||||
assert Media.media_filepaths(media_item) == ["/video/test.mp4"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "create_media_item/1" do
|
||||
|
|
|
|||
|
|
@ -22,6 +22,15 @@ defmodule PinchflatWeb.MediaProfileControllerTest do
|
|||
conn = get(conn, ~p"/media_profiles/new")
|
||||
assert html_response(conn, 200) =~ "New Media Profile"
|
||||
end
|
||||
|
||||
test "renders correct layout when onboarding", %{session_conn: session_conn} do
|
||||
session_conn =
|
||||
session_conn
|
||||
|> put_session(:onboarding, true)
|
||||
|> get(~p"/media_profiles/new")
|
||||
|
||||
refute html_response(session_conn, 200) =~ "MENU"
|
||||
end
|
||||
end
|
||||
|
||||
describe "create media_profile" do
|
||||
|
|
@ -39,6 +48,24 @@ defmodule PinchflatWeb.MediaProfileControllerTest do
|
|||
conn = post(conn, ~p"/media_profiles", media_profile: @invalid_attrs)
|
||||
assert html_response(conn, 200) =~ "New Media Profile"
|
||||
end
|
||||
|
||||
test "redirects to onboarding when onboarding", %{session_conn: session_conn} do
|
||||
session_conn =
|
||||
session_conn
|
||||
|> put_session(:onboarding, true)
|
||||
|> post(~p"/media_profiles", media_profile: @create_attrs)
|
||||
|
||||
assert redirected_to(session_conn) == ~p"/?onboarding=1"
|
||||
end
|
||||
|
||||
test "renders correct layout on error when onboarding", %{session_conn: session_conn} do
|
||||
session_conn =
|
||||
session_conn
|
||||
|> put_session(:onboarding, true)
|
||||
|> post(~p"/media_profiles", media_profile: @invalid_attrs)
|
||||
|
||||
refute html_response(session_conn, 200) =~ "MENU"
|
||||
end
|
||||
end
|
||||
|
||||
describe "edit media_profile" do
|
||||
|
|
|
|||
|
|
@ -1,8 +1,52 @@
|
|||
defmodule PinchflatWeb.PageControllerTest do
|
||||
use PinchflatWeb.ConnCase
|
||||
|
||||
test "GET /", %{conn: conn} do
|
||||
conn = get(conn, ~p"/")
|
||||
assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
|
||||
import Pinchflat.ProfilesFixtures
|
||||
import Pinchflat.MediaSourceFixtures
|
||||
|
||||
describe "GET / when testing onboarding" do
|
||||
test "sets the onboarding session to true when onboarding", %{conn: conn} do
|
||||
conn = get(conn, ~p"/")
|
||||
assert get_session(conn, :onboarding)
|
||||
end
|
||||
|
||||
test "displays the onboarding page when no media profiles exist", %{conn: conn} do
|
||||
conn = get(conn, ~p"/")
|
||||
assert html_response(conn, 200) =~ "Welcome to Pinchflat"
|
||||
end
|
||||
|
||||
test "displays the onboarding page when no sources exist", %{conn: conn} do
|
||||
_ = media_profile_fixture()
|
||||
|
||||
conn = get(conn, ~p"/")
|
||||
assert html_response(conn, 200) =~ "Welcome to Pinchflat"
|
||||
end
|
||||
|
||||
test "displays the onboarding page when onboarding is forced", %{conn: conn} do
|
||||
_ = media_profile_fixture()
|
||||
_ = source_fixture()
|
||||
|
||||
conn = get(conn, ~p"/?onboarding=1")
|
||||
assert html_response(conn, 200) =~ "Welcome to Pinchflat"
|
||||
end
|
||||
|
||||
test "sets the onboarding session to false when not onboarding", %{conn: conn} do
|
||||
conn = get(conn, ~p"/")
|
||||
assert get_session(conn, :onboarding)
|
||||
|
||||
_ = media_profile_fixture()
|
||||
_ = source_fixture()
|
||||
|
||||
conn = get(conn, ~p"/")
|
||||
refute get_session(conn, :onboarding)
|
||||
end
|
||||
|
||||
test "displays the home page when not onboarding", %{conn: conn} do
|
||||
_ = media_profile_fixture()
|
||||
_ = source_fixture()
|
||||
|
||||
conn = get(conn, ~p"/")
|
||||
assert html_response(conn, 200) =~ "MENU"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,6 +38,15 @@ defmodule PinchflatWeb.SourceControllerTest do
|
|||
conn = get(conn, ~p"/sources/new")
|
||||
assert html_response(conn, 200) =~ "New Source"
|
||||
end
|
||||
|
||||
test "renders correct layout when onboarding", %{session_conn: session_conn} do
|
||||
session_conn =
|
||||
session_conn
|
||||
|> put_session(:onboarding, true)
|
||||
|> get(~p"/sources/new")
|
||||
|
||||
refute html_response(session_conn, 200) =~ "MENU"
|
||||
end
|
||||
end
|
||||
|
||||
describe "create source" do
|
||||
|
|
@ -56,6 +65,26 @@ defmodule PinchflatWeb.SourceControllerTest do
|
|||
conn = post(conn, ~p"/sources", source: invalid_attrs)
|
||||
assert html_response(conn, 200) =~ "New Source"
|
||||
end
|
||||
|
||||
test "redirects to onboarding when onboarding", %{session_conn: session_conn, create_attrs: create_attrs} do
|
||||
expect(YtDlpRunnerMock, :run, 1, &runner_function_mock/3)
|
||||
|
||||
session_conn =
|
||||
session_conn
|
||||
|> put_session(:onboarding, true)
|
||||
|> post(~p"/sources", source: create_attrs)
|
||||
|
||||
assert redirected_to(session_conn) == ~p"/?onboarding=1"
|
||||
end
|
||||
|
||||
test "renders correct layout on error when onboarding", %{session_conn: session_conn, invalid_attrs: invalid_attrs} do
|
||||
session_conn =
|
||||
session_conn
|
||||
|> put_session(:onboarding, true)
|
||||
|> post(~p"/sources", source: invalid_attrs)
|
||||
|
||||
refute html_response(session_conn, 200) =~ "MENU"
|
||||
end
|
||||
end
|
||||
|
||||
describe "edit source" do
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ defmodule PinchflatWeb.ConnCase do
|
|||
|
||||
setup tags do
|
||||
Pinchflat.DataCase.setup_sandbox(tags)
|
||||
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||
|
||||
conn = Phoenix.ConnTest.build_conn()
|
||||
session_conn = Plug.Test.init_test_session(conn, %{})
|
||||
|
||||
{:ok, conn: conn, session_conn: session_conn}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue