diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..f5085d3 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,215 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFileExtension, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/.formatter.exs b/.formatter.exs index 602260a..6fc82a9 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -3,5 +3,5 @@ subdirectories: ["priv/*/migrations"], plugins: [Phoenix.LiveView.HTMLFormatter], inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"], - line_length: 100 + line_length: 120 ] diff --git a/.iex.exs b/.iex.exs index e90bc79..4ff1900 100644 --- a/.iex.exs +++ b/.iex.exs @@ -44,7 +44,7 @@ defmodule IexHelpers do :channel -> channel_url() end - SourceDetails.get_video_ids(source) + SourceDetails.get_media_attributes(source) end end diff --git a/lib/pinchflat/media.ex b/lib/pinchflat/media.ex index fe864ae..85529a7 100644 --- a/lib/pinchflat/media.ex +++ b/lib/pinchflat/media.ex @@ -19,15 +19,31 @@ defmodule Pinchflat.Media do @doc """ Returns a list of pending media_items for a given source, where - pending means the `media_filepath` is `nil`. + pending means the `media_filepath` is `nil` AND the media_item + matches the format selection rules of the parent media_profile. + + See `build_format_clauses` but tl;dr is it _may_ filter based + on shorts or livestreams depending on the media_profile settings. Returns [%MediaItem{}, ...]. """ def list_pending_media_items_for(%Source{} = source) do - from( - m in MediaItem, - where: m.source_id == ^source.id and is_nil(m.media_filepath) - ) + media_profile = Repo.preload(source, :media_profile).media_profile + + MediaItem + |> where([mi], mi.source_id == ^source.id and is_nil(mi.media_filepath)) + |> where(^build_format_clauses(media_profile)) + |> Repo.all() + end + + @doc """ + Returns a list of downloaded media_items for a given source. + + Returns [%MediaItem{}, ...]. + """ + def list_downloaded_media_items_for(%Source{} = source) do + MediaItem + |> where([mi], mi.source_id == ^source.id and not is_nil(mi.media_filepath)) |> Repo.all() end @@ -72,4 +88,39 @@ defmodule Pinchflat.Media do def change_media_item(%MediaItem{} = media_item, attrs \\ %{}) do MediaItem.changeset(media_item, attrs) end + + defp build_format_clauses(media_profile) do + mapped_struct = Map.from_struct(media_profile) + + Enum.reduce(mapped_struct, dynamic(true), fn attr, dynamic -> + case {attr, media_profile} do + {{:shorts_behaviour, :only}, %{livestream_behaviour: :only}} -> + dynamic([mi], ^dynamic and (mi.livestream == true or fragment("? ILIKE ?", mi.original_url, "%/shorts/%"))) + + # Technically redundant, but makes the other clauses easier to parse + # (redundant because this condition is the same as the condition above, just flipped) + {{:livestream_behaviour, :only}, %{shorts_behaviour: :only}} -> + dynamic + + {{:shorts_behaviour, :only}, _} -> + # return records with /shorts/ in the original_url + dynamic([mi], ^dynamic and fragment("? ILIKE ?", mi.original_url, "%/shorts/%")) + + {{:livestream_behaviour, :only}, _} -> + # return records with livestream: true + dynamic([mi], ^dynamic and mi.livestream == true) + + {{:shorts_behaviour, :exclude}, %{livestream_behaviour: lb}} when lb != :only -> + # return records without /shorts/ in the original_url + dynamic([mi], ^dynamic and fragment("? NOT ILIKE ?", mi.original_url, "%/shorts/%")) + + {{:livestream_behaviour, :exclude}, %{shorts_behaviour: sb}} when sb != :only -> + # return records with livestream: false + dynamic([mi], ^dynamic and mi.livestream == false) + + _ -> + dynamic + end + end) + end end diff --git a/lib/pinchflat/media/media_item.ex b/lib/pinchflat/media/media_item.ex index 1af9284..d46ca83 100644 --- a/lib/pinchflat/media/media_item.ex +++ b/lib/pinchflat/media/media_item.ex @@ -13,17 +13,21 @@ defmodule Pinchflat.Media.MediaItem do @allowed_fields ~w( title media_id + original_url + livestream media_filepath source_id subtitle_filepaths thumbnail_filepath metadata_filepath )a - @required_fields ~w(media_id source_id)a + @required_fields ~w(title original_url livestream media_id source_id)a schema "media_items" do field :title, :string field :media_id, :string + field :original_url, :string + field :livestream, :boolean, default: false field :media_filepath, :string field :thumbnail_filepath, :string field :metadata_filepath, :string diff --git a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex index 89cb1af..66e56e1 100644 --- a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex +++ b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex @@ -4,18 +4,26 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollection do videos (aka: a source [ie: channels, playlists]). """ - @doc """ - Returns a list of strings representing the video ids in the collection. + alias Pinchflat.Utils.FunctionUtils - Returns {:ok, [binary()]} | {:error, any, ...}. + @doc """ + Returns a list of maps representing the videos in the collection. + + Returns {:ok, [map()]} | {:error, any, ...}. """ - def get_video_ids(url, command_opts \\ []) do + def get_media_attributes(url, command_opts \\ []) do runner = Application.get_env(:pinchflat, :yt_dlp_runner) opts = command_opts ++ [:simulate, :skip_download] - case runner.run(url, opts, "%(id)s") do - {:ok, output} -> {:ok, String.split(output, "\n", trim: true)} - res -> res + case runner.run(url, opts, "%(.{id,title,was_live,original_url})j") do + {:ok, output} -> + output + |> String.split("\n", trim: true) + |> Enum.map(&Phoenix.json_library().decode!/1) + |> FunctionUtils.wrap_ok() + + res -> + res end end diff --git a/lib/pinchflat/media_client/source_details.ex b/lib/pinchflat/media_client/source_details.ex index dcef2d5..488edda 100644 --- a/lib/pinchflat/media_client/source_details.ex +++ b/lib/pinchflat/media_client/source_details.ex @@ -6,11 +6,8 @@ defmodule Pinchflat.MediaClient.SourceDetails do it open-ish for future expansion (just in case). """ - alias Pinchflat.Repo alias Pinchflat.MediaSource.Source - alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection, as: YtDlpSource - alias Pinchflat.Profiles.Options.YtDlp.IndexOptionBuilder, as: YtDlpIndexOptionBuilder @doc """ Gets a source's ID and name from its URL using the given backend. @@ -22,24 +19,19 @@ defmodule Pinchflat.MediaClient.SourceDetails do end @doc """ - Returns a list of video IDs for the given source URL OR source record using the given backend. + Returns a list of basic video data mapsfor the given source URL OR + source record using the given backend. - If passing a source record, the call to the backend may have custom options applied based on - the `option_builder`. - - Returns {:ok, list(binary())} | {:error, any, ...}. + Returns {:ok, [map()]} | {:error, any, ...}. """ - def get_video_ids(sourceable, backend \\ :yt_dlp) + def get_media_attributes(sourceable, backend \\ :yt_dlp) - def get_video_ids(%Source{} = source, backend) do - media_profile = Repo.preload(source, :media_profile).media_profile - {:ok, options} = option_builder(backend).build(media_profile) - - source_module(backend).get_video_ids(source.collection_id, options) + def get_media_attributes(%Source{} = source, backend) do + source_module(backend).get_media_attributes(source.collection_id) end - def get_video_ids(source_url, backend) when is_binary(source_url) do - source_module(backend).get_video_ids(source_url) + def get_media_attributes(source_url, backend) when is_binary(source_url) do + source_module(backend).get_media_attributes(source_url) end defp source_module(backend) do @@ -47,10 +39,4 @@ defmodule Pinchflat.MediaClient.SourceDetails do :yt_dlp -> YtDlpSource end end - - defp option_builder(backend) do - case backend do - :yt_dlp -> YtDlpIndexOptionBuilder - end - end end diff --git a/lib/pinchflat/media_source.ex b/lib/pinchflat/media_source.ex index 8396b8c..2ef7f3a 100644 --- a/lib/pinchflat/media_source.ex +++ b/lib/pinchflat/media_source.ex @@ -7,7 +7,6 @@ defmodule Pinchflat.MediaSource do alias Pinchflat.Repo alias Pinchflat.Tasks - alias Pinchflat.Media alias Pinchflat.Tasks.SourceTasks alias Pinchflat.MediaSource.Source alias Pinchflat.MediaClient.SourceDetails @@ -39,26 +38,6 @@ defmodule Pinchflat.MediaSource do |> commit_and_start_indexing() end - @doc """ - Given a media source, creates (indexes) the media by creating media_items for each - media ID in the source. - - Returns [%MediaItem{}, ...] | [%Ecto.Changeset{}, ...] - """ - def index_media_items(%Source{} = source) do - {:ok, media_ids} = SourceDetails.get_video_ids(source.original_url) - - media_ids - |> Enum.map(fn media_id -> - attrs = %{source_id: source.id, media_id: media_id} - - case Media.create_media_item(attrs) do - {:ok, media_item} -> media_item - {:error, changeset} -> changeset - end - end) - end - @doc """ Updates a source. May attempt to pull additional source details from the original_url (if changed). May attempt to start indexing the source's @@ -101,6 +80,9 @@ defmodule Pinchflat.MediaSource do This means that it'll go for it even if a changeset is otherwise invalid. This is pretty easy to change, but for MVP I'm not concerned. + NOTE: When operating in the ideal path, this effectively adds an API call + to the source creation/update process. Should be used only when needed. + IDEA: Maybe I could discern `collection_type` based on the original URL? It also seems like it's a channel when the returned yt-dlp channel_id is the same as the playlist_id - maybe could use that? diff --git a/lib/pinchflat/profiles/media_profile.ex b/lib/pinchflat/profiles/media_profile.ex index 0d7fad9..e92d1fb 100644 --- a/lib/pinchflat/profiles/media_profile.ex +++ b/lib/pinchflat/profiles/media_profile.ex @@ -45,7 +45,9 @@ defmodule Pinchflat.Profiles.MediaProfile do # through the entire collection to determine if a video is a short or # a livestream. # NOTE: these can BOTH be set to :only which will download shorts and - # livestreams _only_ and ignore regular videos. + # livestreams _only_ and ignore regular videos. The redundant case + # is when one is set to :only and the other is set to :exclude. + # See `build_format_clauses` in the Media context for more. field :shorts_behaviour, Ecto.Enum, values: [:include, :exclude, :only], default: :include field :livestream_behaviour, Ecto.Enum, values: [:include, :exclude, :only], default: :include diff --git a/lib/pinchflat/profiles/options/yt_dlp/index_option_builder.ex b/lib/pinchflat/profiles/options/yt_dlp/index_option_builder.ex deleted file mode 100644 index 2040271..0000000 --- a/lib/pinchflat/profiles/options/yt_dlp/index_option_builder.ex +++ /dev/null @@ -1,52 +0,0 @@ -defmodule Pinchflat.Profiles.Options.YtDlp.IndexOptionBuilder do - @moduledoc """ - Builds the options for yt-dlp to index a media source based on the given media profile. - """ - - alias Pinchflat.Profiles.MediaProfile - - @doc """ - Builds the options for yt-dlp to index a media source based on the given media profile. - """ - def build(%MediaProfile{} = media_profile) do - built_options = release_type_options(media_profile) - - {:ok, built_options} - end - - defp release_type_options(media_profile) do - mapped_struct = Map.from_struct(media_profile) - - # Appending multiple match filters treats them as an OR condition, - # so we have to be careful around combining `only` and `exclude` options. - # eg: only shorts + exclude livestreams = "any video that is a short OR is not a livestream" - # which will return all shorts AND normal videos. - Enum.reduce(mapped_struct, [], fn attr, acc -> - case {attr, media_profile} do - {{:shorts_behaviour, :only}, _} -> - acc ++ [match_filter: "original_url*=/shorts/"] - - {{:livestream_behaviour, :only}, _} -> - acc ++ [match_filter: "was_live"] - - # Since match_filter is an OR (see above), `exclude`s must be ignored entirely if the - # other type is set to `only`. There is also special behaviour if they're both excludes, - # hence why these check against `:include` alone. - {{:shorts_behaviour, :exclude}, %{livestream_behaviour: :include}} -> - acc ++ [match_filter: "original_url!*=/shorts/"] - - {{:livestream_behaviour, :exclude}, %{shorts_behaviour: :include}} -> - acc ++ [match_filter: "!was_live"] - - # Again, since it's an OR, there's a special syntax if they're both excluded - # to make it an AND. Note that I'm not checking for the other permutation of - # both excluding since this MUST get hit so adding the other version would double up. - {{:livestream_behaviour, :exclude}, %{shorts_behaviour: :exclude}} -> - acc ++ [match_filter: "!was_live & original_url!*=/shorts/"] - - _ -> - acc - end - end) - end -end diff --git a/lib/pinchflat/tasks/source_tasks.ex b/lib/pinchflat/tasks/source_tasks.ex index 9d2d8a5..6d16523 100644 --- a/lib/pinchflat/tasks/source_tasks.ex +++ b/lib/pinchflat/tasks/source_tasks.ex @@ -6,6 +6,7 @@ defmodule Pinchflat.Tasks.SourceTasks do alias Pinchflat.Media alias Pinchflat.Tasks alias Pinchflat.MediaSource.Source + alias Pinchflat.MediaClient.SourceDetails alias Pinchflat.Workers.MediaIndexingWorker alias Pinchflat.Workers.VideoDownloadWorker @@ -33,6 +34,32 @@ defmodule Pinchflat.Tasks.SourceTasks do end end + @doc """ + Given a media source, creates (indexes) the media by creating media_items for each + media ID in the source. + + Returns [%MediaItem{}, ...] | [%Ecto.Changeset{}, ...] + """ + def index_media_items(%Source{} = source) do + {:ok, media_attributes} = SourceDetails.get_media_attributes(source.original_url) + + media_attributes + |> Enum.map(fn media_attrs -> + attrs = %{ + source_id: source.id, + title: media_attrs["title"], + media_id: media_attrs["id"], + original_url: media_attrs["original_url"], + livestream: media_attrs["was_live"] + } + + case Media.create_media_item(attrs) do + {:ok, media_item} -> media_item + {:error, changeset} -> changeset + end + end) + end + @doc """ Starts tasks for downloading videos for any of a sources _pending_ media items. Jobs are not enqueued if the source is set to not download media. This will return :ok. diff --git a/lib/pinchflat/utils/function_utils.ex b/lib/pinchflat/utils/function_utils.ex new file mode 100644 index 0000000..c06866f --- /dev/null +++ b/lib/pinchflat/utils/function_utils.ex @@ -0,0 +1,15 @@ +defmodule Pinchflat.Utils.FunctionUtils do + @moduledoc """ + Utility functions for working with functions + """ + + @doc """ + Wraps the provided term in an :ok tuple. Useful for fulfilling a contract, but + other usage should be assessed to see if it's the right fit. + + Returns {:ok, term} + """ + def wrap_ok(value) do + {:ok, value} + end +end diff --git a/lib/pinchflat/workers/media_indexing_worker.ex b/lib/pinchflat/workers/media_indexing_worker.ex index f46e427..e8e5b37 100644 --- a/lib/pinchflat/workers/media_indexing_worker.ex +++ b/lib/pinchflat/workers/media_indexing_worker.ex @@ -47,7 +47,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do end defp index_media_and_reschedule(source) do - MediaSource.index_media_items(source) + SourceTasks.index_media_items(source) SourceTasks.enqueue_pending_media_downloads(source) source diff --git a/lib/pinchflat_web/components/core_components.ex b/lib/pinchflat_web/components/core_components.ex index cc65540..5c7c073 100644 --- a/lib/pinchflat_web/components/core_components.ex +++ b/lib/pinchflat_web/components/core_components.ex @@ -168,8 +168,7 @@ defmodule PinchflatWeb.CoreComponents do phx-connected={hide("#server-error")} hidden > - Hang in there while we get back on track - <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> + Hang in there while we get back on track <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> """ @@ -245,8 +244,7 @@ defmodule PinchflatWeb.CoreComponents do values: ~w(checkbox color date datetime-local email file hidden month number password 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]" + attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:email]" attr :errors, :list, default: [] attr :checked, :boolean, doc: "the checked flag for checkbox inputs" @@ -254,8 +252,7 @@ defmodule PinchflatWeb.CoreComponents do attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2" attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs" - attr :rest, :global, - include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength + attr :rest, :global, include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength multiple pattern placeholder readonly required rows size step) slot :inner_block @@ -279,15 +276,7 @@ defmodule PinchflatWeb.CoreComponents do
<.help :if={@help}><%= @help %> @@ -318,8 +307,7 @@ defmodule PinchflatWeb.CoreComponents do {@rest} />
-
-
+
- + <%= render_slot(action, @row_item.(row)) %>
@@ -607,10 +592,7 @@ defmodule PinchflatWeb.CoreComponents do def back(assigns) do ~H"""
- <.link - navigate={@navigate} - class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" - > + <.link navigate={@navigate} class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"> <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> <%= render_slot(@inner_block) %> @@ -651,8 +633,7 @@ defmodule PinchflatWeb.CoreComponents do JS.show(js, to: selector, transition: - {"transition-all transform ease-out duration-300", - "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + {"transition-all transform ease-out duration-300", "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", "opacity-100 translate-y-0 sm:scale-100"} ) end diff --git a/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex b/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex index 8f27e76..5b7fe3c 100644 --- a/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex +++ b/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex @@ -17,11 +17,7 @@
    <.sidebar_item icon="hero-tv" text="Sources" navigate={~p"/sources"} /> - <.sidebar_item - icon="hero-adjustments-vertical" - text="Media Profiles" - navigate={~p"/media_profiles"} - /> + <.sidebar_item icon="hero-adjustments-vertical" text="Media Profiles" navigate={~p"/media_profiles"} />
diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex index 9286ec3..78ca9f3 100644 --- a/lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex +++ b/lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex @@ -8,11 +8,7 @@
- <.source_form - changeset={@changeset} - media_profiles={@media_profiles} - action={~p"/sources/#{@source}"} - /> + <.source_form changeset={@changeset} media_profiles={@media_profiles} action={~p"/sources/#{@source}"} />
diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex index 8b74117..796dc4b 100644 --- a/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex +++ b/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex @@ -29,16 +29,10 @@ <: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" - > + <.link navigate={~p"/sources/#{source.id}"} class="hover:text-secondary duration-200 ease-in-out mx-0.5"> <.icon name="hero-eye" /> - <.link - navigate={~p"/sources/#{source.id}/edit"} - class="hover:text-secondary duration-200 ease-in-out mx-0.5" - > + <.link navigate={~p"/sources/#{source.id}/edit"} class="hover:text-secondary duration-200 ease-in-out mx-0.5"> <.icon name="hero-pencil-square" /> diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex index 4cc1499..4244d5d 100644 --- a/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex +++ b/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex @@ -3,12 +3,7 @@ Oops, something went wrong! Please check the errors below. - <.input - field={f[:original_url]} - type="text" - label="Source URL" - help="URL of a channel or playlist (required)" - /> + <.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" /> @@ -19,12 +14,7 @@ label="Media Profile" /> - <.input - field={f[:collection_type]} - options={friendly_collection_types()} - type="select" - label="Source Type" - /> + <.input field={f[:collection_type]} options={friendly_collection_types()} type="select" label="Source Type" /> <.input field={f[:index_frequency_minutes]} diff --git a/lib/pinchflat_web/controllers/page_html/home.html.heex b/lib/pinchflat_web/controllers/page_html/home.html.heex index e9fc48d..5aa726d 100644 --- a/lib/pinchflat_web/controllers/page_html/home.html.heex +++ b/lib/pinchflat_web/controllers/page_html/home.html.heex @@ -136,11 +136,7 @@ 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" > -