diff --git a/example_project/.gitignore b/example_project/.gitignore index 5ea471f..04771d2 100644 --- a/example_project/.gitignore +++ b/example_project/.gitignore @@ -40,3 +40,8 @@ npm-debug.log # Ignore ssr build for svelte. /priv/svelte/ + +# SQLite databases +*.db +*.db-shm +*.db-wal diff --git a/example_project/assets/package-lock.json b/example_project/assets/package-lock.json index 4d273a3..9c14b82 100644 --- a/example_project/assets/package-lock.json +++ b/example_project/assets/package-lock.json @@ -25,7 +25,7 @@ } }, "../..": { - "version": "0.15.0-rc.6", + "version": "0.17.0", "license": "MIT", "devDependencies": { "prettier": "3.3.3", @@ -38,7 +38,7 @@ "license": "MIT" }, "../deps/phoenix": { - "version": "1.7.14", + "version": "1.7.21", "license": "MIT" }, "../deps/phoenix_html": { @@ -581,6 +581,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -735,6 +736,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -1198,6 +1200,7 @@ "integrity": "sha512-MuzIIVRSbc8XxHH7FjkvWqkIcr1BvoMZoR/oFuAJDlh7VSaNJzrB4uJ38GRQa+mWjLXODAMzeDe0xi9GYbGwnw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "css": "^3.0.0", "debug": "~3.1.0", @@ -1221,6 +1224,7 @@ "integrity": "sha512-56Vd/nwJrljV0w7RCV1A8sB4/yjSbWW5qrGDTAzp7q42OxwqEWT+6obWzDt41tHjIW+C9Fs2ygtejjJrXR+ZPA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1316,6 +1320,7 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/example_project/assets/svelte/NotesApp.svelte b/example_project/assets/svelte/NotesApp.svelte new file mode 100644 index 0000000..09a680e --- /dev/null +++ b/example_project/assets/svelte/NotesApp.svelte @@ -0,0 +1,239 @@ + + + + Notes ({encoder}) + + +
+ +
+
+ + {encoder} JSON Encoder + +
+

{info}

+
+ + +
{ + e.preventDefault() + handleSubmit() + }} + class="mb-8 p-4 bg-white rounded-lg shadow-sm border" + > +

Create Note

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+ {#each colors as c} + + {/each} +
+
+ + +
+
+ + + + + + {#if notes.length > 0} +
+ {notes.length} note{notes.length === 1 ? "" : "s"} +
+ {/if} +
diff --git a/example_project/config/dev.exs b/example_project/config/dev.exs index bd8c3b1..2730079 100644 --- a/example_project/config/dev.exs +++ b/example_project/config/dev.exs @@ -2,13 +2,10 @@ import Config # Configure your database config :example, Example.Repo, - username: "postgres", - password: "postgres", - hostname: "localhost", - database: "example_dev", + database: Path.expand("../example_dev.db", __DIR__), + pool_size: 5, stacktrace: true, - show_sensitive_data_on_connection_error: true, - pool_size: 10 + show_sensitive_data_on_connection_error: true # For development, we disable any cache and enable # debugging and code reloading. diff --git a/example_project/config/test.exs b/example_project/config/test.exs index 78e2b9c..3f16d36 100644 --- a/example_project/config/test.exs +++ b/example_project/config/test.exs @@ -6,12 +6,9 @@ import Config # to provide built-in test partitioning in CI environment. # Run `mix help test` for more information. config :example, Example.Repo, - username: "postgres", - password: "postgres", - hostname: "localhost", - database: "example_test#{System.get_env("MIX_TEST_PARTITION")}", + database: Path.expand("../example_test#{System.get_env("MIX_TEST_PARTITION")}.db", __DIR__), pool: Ecto.Adapters.SQL.Sandbox, - pool_size: 10 + pool_size: 5 # We don't run a server during test. If one is required, # you can enable the server option below. diff --git a/example_project/lib/example/application.ex b/example_project/lib/example/application.ex index 12aa4ef..7269fd2 100644 --- a/example_project/lib/example/application.ex +++ b/example_project/lib/example/application.ex @@ -11,8 +11,8 @@ defmodule Example.Application do {NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]}, # Start the Telemetry supervisor ExampleWeb.Telemetry, - # Start the Ecto repository (actually not used in this example, so skip it) - # Example.Repo, + # Start the Ecto repository + Example.Repo, # Start the PubSub system {Phoenix.PubSub, name: Example.PubSub}, # Start Finch diff --git a/example_project/lib/example/note.ex b/example_project/lib/example/note.ex new file mode 100644 index 0000000..713da90 --- /dev/null +++ b/example_project/lib/example/note.ex @@ -0,0 +1,28 @@ +defmodule Example.Note do + @moduledoc """ + Ecto schema for notes with UUID primary key. + + This schema demonstrates how Ecto structs work with LiveSvelte's + OTP JSON encoder, which automatically converts structs to maps. + """ + use Ecto.Schema + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + + schema "notes" do + field :title, :string + field :content, :string + field :color, :string, default: "#fef3c7" + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(note, attrs) do + note + |> cast(attrs, [:title, :content, :color]) + |> validate_required([:title]) + |> validate_length(:title, max: 100) + |> validate_length(:content, max: 1000) + end +end diff --git a/example_project/lib/example/notes.ex b/example_project/lib/example/notes.ex new file mode 100644 index 0000000..877a63e --- /dev/null +++ b/example_project/lib/example/notes.ex @@ -0,0 +1,55 @@ +defmodule Example.Notes do + @moduledoc """ + Context module for managing notes. + """ + import Ecto.Query + alias Example.Repo + alias Example.Note + + @doc """ + Returns all notes ordered by creation date (newest first). + """ + def list_notes do + Note + |> order_by(desc: :inserted_at) + |> Repo.all() + end + + @doc """ + Gets a single note by ID. + Raises `Ecto.NoResultsError` if the note does not exist. + """ + def get_note!(id), do: Repo.get!(Note, id) + + @doc """ + Creates a new note. + """ + def create_note(attrs \\ %{}) do + %Note{} + |> Note.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates an existing note. + """ + def update_note(%Note{} = note, attrs) do + note + |> Note.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a note. + """ + def delete_note(%Note{} = note) do + Repo.delete(note) + end + + @doc """ + Returns a changeset for tracking note changes. + """ + def change_note(%Note{} = note, attrs \\ %{}) do + Note.changeset(note, attrs) + end +end diff --git a/example_project/lib/example/repo.ex b/example_project/lib/example/repo.ex index 8aa05c9..ebed807 100644 --- a/example_project/lib/example/repo.ex +++ b/example_project/lib/example/repo.ex @@ -1,5 +1,5 @@ defmodule Example.Repo do use Ecto.Repo, otp_app: :example, - adapter: Ecto.Adapters.Postgres + adapter: Ecto.Adapters.SQLite3 end diff --git a/example_project/lib/example_web/components/core_components.ex b/example_project/lib/example_web/components/core_components.ex index de0e4a1..a87b93a 100644 --- a/example_project/lib/example_web/components/core_components.ex +++ b/example_project/lib/example_web/components/core_components.ex @@ -686,4 +686,139 @@ defmodule ExampleWeb.CoreComponents do def translate_errors(errors, field) when is_list(errors) do for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) end + + @doc """ + Renders a navigation link with active state styling. + + ## Examples + + <.nav_link href={~p"/home"} current_path={@current_path}>Home + """ + attr :href, :string, required: true + attr :current_path, :string, required: true + attr :class, :string, default: nil + slot :inner_block, required: true + + def nav_link(assigns) do + ~H""" + + <%= render_slot(@inner_block) %> + + """ + end + + @doc """ + Renders a mobile navigation panel with slide-out sidebar. + + ## Examples + + <.mobile_nav id="mobile-nav" current_path={@current_path}> + <:section title="Basics"> + <.nav_link href={~p"/hello"} current_path={@current_path}>Hello + + + """ + attr :id, :string, required: true + attr :current_path, :string, required: true + + slot :section, required: true do + attr :title, :string, required: true + end + + def mobile_nav(assigns) do + ~H""" +