Music streaming on ATProto!

feat: add hologram and tutorial app

ovyerus.com f10bc0f5 4b70a7b5

verified
Changed files
+137 -39
.vscode
assets
css
lib
comet_app
comet_web
+1 -1
.formatter.exs
··· 1 1 [ 2 - import_deps: [:ecto, :ecto_sql, :phoenix], 2 + import_deps: [:ecto, :ecto_sql, :phoenix, :hologram], 3 3 subdirectories: ["priv/*/migrations"], 4 4 plugins: [Phoenix.LiveView.HTMLFormatter], 5 5 inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
+1 -34
.gitignore
··· 1 1 node_modules 2 - 3 - # Output 4 2 .output 5 3 .vercel 6 4 .netlify ··· 8 6 .svelte-kit 9 7 build 10 8 .elixir_ls 11 - 12 - # OS 13 9 .DS_Store 14 10 Thumbs.db 15 - 16 - # Env 17 11 .env 18 12 .env.* 19 13 !.env.example 20 14 !.env.test 21 15 .envrc 22 16 .direnv 23 - 24 - # Vite 25 17 vite.config.js.timestamp-* 26 18 vite.config.ts.timestamp-* 27 - 28 - # Nix 29 19 result 30 - 31 - # The directory Mix will write compiled artifacts to. 32 20 /_build/ 33 - 34 - # If you run "mix test --cover", coverage assets end up here. 35 21 /cover/ 36 - 37 - # The directory Mix downloads your dependencies sources to. 38 22 /deps/ 39 - 40 - # Where 3rd-party dependencies like ExDoc output generated docs. 41 23 /doc/ 42 - 43 - # Ignore .fetch files in case you like to edit your project deps locally. 44 24 /.fetch 45 - 46 - # If the VM crashes, it generates a dump, let's ignore it too. 47 25 erl_crash.dump 48 - 49 - # Also ignore archive artifacts (built via "mix archive.build"). 50 26 *.ez 51 - 52 - # Temporary files, for example, from tests. 53 27 /tmp/ 54 - 55 - # Ignore package tarball (built via "mix hex.build"). 56 28 comet-*.tar 57 - 58 - # Ignore assets that are produced by build tools. 59 29 /priv/static/assets/ 60 - 61 - # Ignore digested assets cache. 30 + /priv/static/hologram/ 62 31 /priv/static/cache_manifest.json 63 - 64 - # In case you use Node.js/npm, you want to ignore these. 65 32 npm-debug.log 66 33 /assets/node_modules/ 67 34
+5 -1
.vscode/settings.json
··· 4 4 "fileMatch": ["/lexicons/*/**/*.json"], 5 5 "url": "https://gist.githubusercontent.com/mary-ext/6e428031c18799d1587048b456d118cb/raw/4322c492384ac5da33986dee9588938a88d922f1/schema.json" 6 6 } 7 - ], 7 + ], 8 + "files.associations": { 9 + "*.holo": "phoenix-heex" 10 + }, 11 + 8 12 }
+1
assets/css/app.css
··· 4 4 @import "tailwindcss" source(none); 5 5 @source "../css"; 6 6 @source "../js"; 7 + @source "../../lib/comet_app"; 7 8 @source "../../lib/comet_web"; 8 9 9 10 /* A Tailwind plugin that makes "hero-#{ICON}" classes available.
+16
lib/comet_app/components/post_preview.ex
··· 1 + defmodule CometApp.Components.PostPreview do 2 + use Hologram.Component 3 + alias Hologram.UI.Link 4 + 5 + prop :post, :map 6 + 7 + def template do 8 + ~HOLO""" 9 + <article class="post-preview"> 10 + <h2>{@post.title}</h2> 11 + <p>{@post.excerpt}</p> 12 + <Link to={CometApp.PostPage, id: @post.id}>Read more</Link> 13 + </article> 14 + """ 15 + end 16 + end
+6
lib/comet_app/layouts/main.ex
··· 1 + defmodule CometApp.MainLayout do 2 + use Hologram.Component 3 + 4 + alias Hologram.UI.Link 5 + alias Hologram.UI.Runtime 6 + end
+18
lib/comet_app/layouts/main.holo
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 + <title>Comet App</title> 7 + <link phx-track-static rel="stylesheet" href="/assets/css/app.css" /> 8 + <Runtime /> 9 + </head> 10 + <body> 11 + <nav class="bg-red-500 text-2xl"> 12 + <Link to={CometApp.HomePage}>Home</Link> 13 + </nav> 14 + <main> 15 + <slot /> 16 + </main> 17 + </body> 18 + </html>
+30
lib/comet_app/pages/home.ex
··· 1 + defmodule CometApp.HomePage do 2 + use Hologram.Page 3 + alias CometApp.Components.PostPreview 4 + 5 + route "/" 6 + 7 + layout CometApp.MainLayout 8 + 9 + def init(_params, component, _server) do 10 + # In real app, fetch from database 11 + posts = [ 12 + %{id: 1, title: "First Post", excerpt: "This is my first post"}, 13 + %{id: 2, title: "Second Post", excerpt: nil} 14 + ] 15 + 16 + put_state(component, :posts, posts) 17 + end 18 + 19 + def template do 20 + ~HOLO""" 21 + <h1>Welcome to my Blog</h1> 22 + 23 + <div class="posts"> 24 + {%for post <- @posts} 25 + <PostPreview post={post} /> 26 + {/for} 27 + </div> 28 + """ 29 + end 30 + end
+48
lib/comet_app/pages/post_view.ex
··· 1 + defmodule CometApp.PostPage do 2 + use Hologram.Page 3 + 4 + route "/posts/:id" 5 + 6 + param :id, :integer 7 + 8 + layout CometApp.MainLayout 9 + 10 + def init(params, component, _server) do 11 + # In real app, fetch from database 12 + post = %{ 13 + id: params.id, 14 + title: "Example Post", 15 + content: "This is the full content...", 16 + likes: 0 17 + } 18 + 19 + put_state(component, :post, post) 20 + end 21 + 22 + def template do 23 + ~HOLO""" 24 + <article> 25 + <h1>{@post.title}</h1> 26 + <p>{@post.content}</p> 27 + 28 + <div class="likes"> 29 + Likes: {@post.likes} 30 + <button $click="like_post">Like</button> 31 + </div> 32 + </article> 33 + """ 34 + end 35 + 36 + def action(:like_post, _params, component) do 37 + # Update likes locally first for instant feedback 38 + component 39 + |> put_state([:post, :likes], component.state.post.likes + 1) 40 + |> put_command(:save_like, post_id: component.state.post.id) 41 + end 42 + 43 + def command(:save_like, params, server) do 44 + # In real app, save to database 45 + IO.puts("Liked post #{params.post_id}") 46 + server 47 + end 48 + end
+2 -1
lib/comet_web/endpoint.ex
··· 24 24 at: "/", 25 25 from: :comet, 26 26 gzip: not code_reloading?, 27 - only: CometWeb.static_paths(), 27 + only: ["hologram" | CometWeb.static_paths()], 28 28 raise_on_missing_only: code_reloading? 29 29 30 30 # Code reloading can be explicitly enabled under the ··· 51 51 plug Plug.MethodOverride 52 52 plug Plug.Head 53 53 plug Plug.Session, @session_options 54 + plug Hologram.Router 54 55 plug CometWeb.Router 55 56 end
+3 -2
mix.exs
··· 10 10 start_permanent: Mix.env() == :prod, 11 11 aliases: aliases(), 12 12 deps: deps(), 13 - compilers: [:phoenix_live_view] ++ Mix.compilers(), 13 + compilers: [:phoenix_live_view, :hologram] ++ Mix.compilers(), 14 14 listeners: [Phoenix.CodeReloader] 15 15 ] 16 16 end ··· 69 69 {:atex, "~> 0.6"}, 70 70 {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, 71 71 {:drinkup, "~> 0.1"}, 72 - {:typedstruct, "~> 0.5"} 72 + {:typedstruct, "~> 0.5"}, 73 + {:hologram, "~> 0.6.5"} 73 74 ] 74 75 end 75 76
+6
mix.lock
··· 1 1 %{ 2 2 "atex": {:hex, :atex, "0.6.0", "a02f3c1b3ef04d8cd30243a05fc3629929c66d826e1d6a7e9d4ec6076ac89aea", [:mix], [{:ex_cldr, "~> 2.42", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}, {:multiformats_ex, "~> 0.2", [hex: :multiformats_ex, repo: "hexpm", optional: false]}, {:peri, "~> 0.6", [hex: :peri, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:recase, "~> 0.5", [hex: :recase, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}], "hexpm", "a3615e361e1e1b2887910834b3ede88680a02e4d585243ea90d0a6394f688aa2"}, 3 3 "bandit": {:hex, :bandit, "1.8.0", "c2e93d7e3c5c794272fa4623124f827c6f24b643acc822be64c826f9447d92fb", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8458ff4eed20ff2a2ea69d4854883a077c33ea42b51f6811b044ceee0fa15422"}, 4 + "beam_file": {:hex, :beam_file, "0.6.2", "efd54ec60be6a03f0a8f96f72b0353427196613289c46032d3500f0ab6c34d32", [:mix], [], "hexpm", "09a99e8e5aad674edcad7213b0d7602375dfd3c7d02f8e3136e3efae0bcc9c56"}, 4 5 "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 5 6 "car": {:hex, :car, "0.1.1", "a5bc4c5c1be96eab437634b3c0ccad1fe17b5e3d68c22a4031241ae1345aebd4", [:mix], [{:cbor, "~> 1.0.0", [hex: :cbor, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}, {:varint, "~> 1.4", [hex: :varint, repo: "hexpm", optional: false]}], "hexpm", "f895dda8123d04dd336db5a2bf0d0b47f4559cd5383f83fcca0700c1b45bfb6a"}, 6 7 "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, ··· 23 24 "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, 24 25 "fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"}, 25 26 "gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"}, 27 + "gproc": {:hex, :gproc, "1.0.0", "aa9ec57f6c9ff065b16d96924168d7c7157cd1fd457680efe4b1274f456fa500", [:rebar3], [], "hexpm", "109f253c2787de8a371a51179d4973230cbec6239ee673fa12216a5ce7e4f902"}, 26 28 "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, 27 29 "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]}, 30 + "hologram": {:hex, :hologram, "0.6.5", "355312e414489f590520efa2ccccf1719c415793225a87330407772efcaa0a78", [:mix], [{:beam_file, "0.6.2", [hex: :beam_file, repo: "hexpm", optional: false]}, {:file_system, "~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:gproc, "~> 1.0", [hex: :gproc, repo: "hexpm", optional: false]}, {:html_entities, "~> 0.5", [hex: :html_entities, repo: "hexpm", optional: false]}, {:interceptor, "~> 0.5", [hex: :interceptor, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:uuid, "~> 1.0", [hex: :uuid, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8b351690087b9980e60d4a2992cfceb4637e2a2082afdfc78060f569299abdc6"}, 28 31 "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, 32 + "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, 29 33 "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 34 + "interceptor": {:hex, :interceptor, "0.5.4", "158e019413439714306d52ff5189533b1236ab776b6f7a1ce63ace079f84867c", [:mix], [], "hexpm", "a4fa78a73199832222c4bb47c57e5b7384f7b49d43c09a57c6e731086f219f95"}, 30 35 "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 31 36 "jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"}, 32 37 "lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"}, ··· 57 62 "thousand_island": {:hex, :thousand_island, "1.4.2", "735fa783005d1703359bbd2d3a5a3a398075ba4456e5afe3c5b7cf4666303d36", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1c7637f16558fc1c35746d5ee0e83b18b8e59e18d28affd1f2fa1645f8bc7473"}, 58 63 "typedstruct": {:hex, :typedstruct, "0.5.4", "d1d33d58460a74f413e9c26d55e66fd633abd8ac0fb12639add9a11a60a0462a", [:make, :mix], [], "hexpm", "ffaef36d5dbaebdbf4ed07f7fb2ebd1037b2c1f757db6fb8e7bcbbfabbe608d8"}, 59 64 "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, 65 + "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, 60 66 "varint": {:hex, :varint, "1.5.1", "17160c70d0428c3f8a7585e182468cac10bbf165c2360cf2328aaa39d3fb1795", [:mix], [], "hexpm", "24f3deb61e91cb988056de79d06f01161dd01be5e0acae61d8d936a552f1be73"}, 61 67 "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, 62 68 "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},