improvement: support PhoenixHTML 4.0

fix: ensure starting state of dropdowns is honored
This commit is contained in:
Zach Daniel 2024-01-08 19:53:39 -05:00
parent 0e86459deb
commit 92f9997fa3
9 changed files with 451 additions and 61 deletions

View file

@ -0,0 +1,39 @@
defmodule Demo.Repo.Migrations.MigrateResources2 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:users) do
add :type, :text
end
drop_if_exists unique_index(:users, [:first_name, :last_name],
name: "users_representative_name_index"
)
create unique_index(:users, [:id, :first_name, :last_name],
where: "representative = true",
name: "users_representative_name_index"
)
end
def down do
drop_if_exists unique_index(:users, [:id, :first_name, :last_name],
name: "users_representative_name_index"
)
create unique_index(:users, [:id, :first_name, :last_name],
where: "representative = true",
name: "users_representative_name_index"
)
alter table(:users) do
remove :type
end
end
end

View file

@ -0,0 +1,23 @@
defmodule Demo.Repo.Migrations.MigrateResources3 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:users) do
modify :type, :text, default: "type1"
add :types, {:array, :text}
end
end
def down do
alter table(:users) do
remove :types
modify :type, :text, default: nil
end
end
end

View file

@ -0,0 +1,165 @@
{
"attributes": [
{
"default": "fragment(\"uuid_generate_v4()\")",
"size": null,
"type": "uuid",
"source": "id",
"references": null,
"allow_nil?": false,
"primary_key?": true,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "first_name",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "last_name",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "boolean",
"source": "representative",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "false",
"size": null,
"type": "boolean",
"source": "admin",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "api_key",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "date",
"source": "date_of_birth",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "map",
"source": "profile",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": [
"array",
"map"
],
"source": "alternate_profiles",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "type",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": [
"array",
"text"
],
"source": "tags",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "fragment(\"now()\")",
"size": null,
"type": "utc_datetime_usec",
"source": "inserted_at",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "fragment(\"now()\")",
"size": null,
"type": "utc_datetime_usec",
"source": "updated_at",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
}
],
"table": "users",
"hash": "ABBC7E1B2B4BC0B24C2CBF2C08AFA65AD4A8F2D3FA6659DA9E5B7A5413CA421B",
"repo": "Elixir.Demo.Repo",
"multitenancy": {
"global": true,
"attribute": "id",
"strategy": "attribute"
},
"schema": null,
"check_constraints": [],
"identities": [
{
"name": "representative_name",
"keys": [
"first_name",
"last_name"
],
"base_filter": "representative = true",
"index_name": "users_representative_name_index"
}
],
"custom_indexes": [],
"base_filter": null,
"custom_statements": [],
"has_create_action": true
}

View file

@ -0,0 +1,178 @@
{
"attributes": [
{
"default": "fragment(\"uuid_generate_v4()\")",
"size": null,
"type": "uuid",
"source": "id",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": true
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "first_name",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "last_name",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "boolean",
"source": "representative",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "false",
"size": null,
"type": "boolean",
"source": "admin",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "api_key",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "date",
"source": "date_of_birth",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "map",
"source": "profile",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": [
"array",
"map"
],
"source": "alternate_profiles",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "\"type1\"",
"size": null,
"type": "text",
"source": "type",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": [
"array",
"text"
],
"source": "types",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": [
"array",
"text"
],
"source": "tags",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "fragment(\"now()\")",
"size": null,
"type": "utc_datetime_usec",
"source": "inserted_at",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": false
},
{
"default": "fragment(\"now()\")",
"size": null,
"type": "utc_datetime_usec",
"source": "updated_at",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": false
}
],
"table": "users",
"hash": "055B8485A4BA9B34AEA45E5B06E8A8FD16E5356C38B3B698A2BE19325C171DE4",
"repo": "Elixir.Demo.Repo",
"schema": null,
"identities": [
{
"name": "representative_name",
"keys": [
"first_name",
"last_name"
],
"base_filter": "representative = true",
"index_name": "users_representative_name_index"
}
],
"multitenancy": {
"global": true,
"attribute": "id",
"strategy": "attribute"
},
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true
}

View file

@ -111,6 +111,14 @@ defmodule Demo.Accounts.User do
attribute :profile, Demo.Accounts.Profile attribute :profile, Demo.Accounts.Profile
attribute :alternate_profiles, {:array, Demo.Accounts.Profile} attribute :alternate_profiles, {:array, Demo.Accounts.Profile}
attribute :type, :atom do
constraints one_of: [:type1, :type2]
default :type1
end
attribute :types, {:array, :atom} do
constraints items: [one_of: [:type1, :type2]]
end
attribute :tags, {:array, :string} attribute :tags, {:array, :string}
timestamps() timestamps()

View file

@ -109,10 +109,13 @@ defmodule AshAdmin.Components.Resource.Form do
id="_action_form" id="_action_form"
> >
<label for="action">Action</label> <label for="action">Action</label>
<%= Phoenix.HTML.Form.select(form, :action, actions(@resource, @type), <.input
disabled: Enum.count(actions(@resource, @type)) <= 1, type="select"
selected: to_string(@action.name) field={form[:action]}
) %> disabled={Enum.count(actions(@resource, @type)) <= 1}
options={actions(@resource, @type)}
value={to_string(@action.name)}
/>
</.form> </.form>
</div> </div>
<div class="px-4 py-5 sm:p-6"> <div class="px-4 py-5 sm:p-6">
@ -677,14 +680,13 @@ defmodule AshAdmin.Components.Resource.Form do
assigns = assign(assigns, attribute: attribute, form: form, value: value, name: name) assigns = assign(assigns, attribute: attribute, form: form, value: value, name: name)
~H""" ~H"""
<%= Phoenix.HTML.Form.select( <.input
@form, type="select"
@attribute.name, id={@form.id <> "_#{@attribute.name}"}
[True: "true", False: "false"], name={@name || @form.name <> "[#{@attribute.name}]"}
prompt: allow_nil_option(@attribute, @value), options={[True: "true", False: "false"]}
selected: value(@value, @form, @attribute, "true"), value={value(@value, @form, @attribute, "true")}
name: @name || @form.name <> "[#{@attribute.name}]" />
) %>
""" """
end end
@ -718,14 +720,14 @@ defmodule AshAdmin.Components.Resource.Form do
~H""" ~H"""
<%= cond do %> <%= cond do %>
<% @type == Ash.Type.Atom && @attribute.constraints[:one_of] -> %> <% @type == Ash.Type.Atom && @attribute.constraints[:one_of] -> %>
<%= Phoenix.HTML.Form.select( <.input
@form, type="select"
@attribute.name, id={@form.id <> "_#{@attribute.name}"}
Enum.map(@attribute.constraints[:one_of], &{to_name(&1), &1}), options={Enum.map(@attribute.constraints[:one_of] || [], &{to_name(&1), &1})}
selected: value(@value, @form, @attribute, List.first(@attribute.constraints[:one_of])), value={value(@value, @form, @attribute, List.first(@attribute.constraints[:one_of] || []))}
prompt: allow_nil_option(@attribute, @value), prompt={allow_nil_option(@attribute, @value)}
name: @name || @form.name <> "[#{@attribute.name}]" name={@name || @form.name <> "[#{@attribute.name}]"}
) %> />
<% markdown?(@form.source.resource, @attribute) -> %> <% markdown?(@form.source.resource, @attribute) -> %>
<div <div
phx-hook="MarkdownEditor" phx-hook="MarkdownEditor"
@ -874,14 +876,14 @@ defmodule AshAdmin.Components.Resource.Form do
<.icon name="hero-plus" class="h-4 w-4 text-gray-500" /> <.icon name="hero-plus" class="h-4 w-4 text-gray-500" />
</button> </button>
<% is_atom(@attribute.type) && function_exported?(@attribute.type, :values, 0) -> %> <% is_atom(@attribute.type) && function_exported?(@attribute.type, :values, 0) -> %>
<%= Phoenix.HTML.Form.select( <.input
@form, type="select"
@attribute.name, id={@form.id <> "_#{@attribute.name}"}
Enum.map(@attribute.type.values(), &{to_name(&1), &1}), options={Enum.map(@attribute.type.values(), &{to_name(&1), &1})}
selected: value(@value, @form, @attribute, List.first(@attribute.type.values())), value={value(@value, @form, @attribute, List.first(@attribute.type.values()))}
prompt: allow_nil_option(@attribute, @value), prompt={allow_nil_option(@attribute, @value)}
name: @name || @form.name <> "[#{@attribute.name}]" name={@name || @form.name <> "[#{@attribute.name}]"}
) %> />
<% true -> %> <% true -> %>
<%= render_fallback_attribute(assigns, @form, @attribute, @value, @name) %> <%= render_fallback_attribute(assigns, @form, @attribute, @value, @name) %>
<% end %> <% end %>
@ -898,11 +900,7 @@ defmodule AshAdmin.Components.Resource.Form do
<div> <div>
<div :for={ <div :for={
{this_value, index} <- {this_value, index} <-
Enum.with_index( Enum.with_index(list_value(@value || AshPhoenix.Form.value(@form.source, @attribute.name)))
list_value(
@value || Phoenix.HTML.FormData.input_value(@form.source, @form, @attribute.name)
)
)
}> }>
<%= render_attribute_input( <%= render_attribute_input(
assigns, assigns,
@ -1023,38 +1021,17 @@ defmodule AshAdmin.Components.Resource.Form do
defp value(value, form, attribute, default \\ nil) defp value(value, form, attribute, default \\ nil)
defp value({:list_value, nil}, _, _, default), do: default
defp value({:list_value, value}, _, _, _), do: value defp value({:list_value, value}, _, _, _), do: value
defp value(value, _form, _attribute, _) when not is_nil(value), do: value defp value(value, _form, _attribute, _) when not is_nil(value), do: value
defp value(_value, form, attribute, default) do defp value(_value, form, attribute, _default) do
value = Phoenix.HTML.FormData.input_value(form.source, form, attribute.name) AshPhoenix.Form.value(form.source, attribute.name)
case value do
nil ->
case attribute.default do
nil ->
default
func when is_function(func) ->
default
attribute_default ->
attribute_default
end
value ->
value
end
end end
defp allow_nil_option(_, {:list_value, _}), do: "-" defp allow_nil_option(_, {:list_value, _}), do: "-"
defp allow_nil_option(%{allow_nil?: true}, _), do: "-" defp allow_nil_option(%{allow_nil?: true}, _), do: "-"
defp allow_nil_option(%{default: default, allow_nil?: false}, _) when not is_nil(default),
do: nil
defp allow_nil_option(_, _), do: "Select an option" defp allow_nil_option(_, _), do: "Select an option"
defp can_append_embed?(changeset, attribute) do defp can_append_embed?(changeset, attribute) do
@ -1192,8 +1169,7 @@ defmodule AshAdmin.Components.Resource.Form do
fn adding_form -> fn adding_form ->
new_value = new_value =
adding_form adding_form
|> Phoenix.HTML.Form.form_for("foo") |> AshPhoenix.Form.value(String.to_existing_atom(field))
|> Phoenix.HTML.Form.input_value(String.to_existing_atom(field))
|> Kernel.||([]) |> Kernel.||([])
|> indexed_list() |> indexed_list()
|> append_to_and_map(nil) |> append_to_and_map(nil)

View file

@ -1,6 +1,7 @@
defmodule AshAdmin.Components.Resource.SelectTable do defmodule AshAdmin.Components.Resource.SelectTable do
@moduledoc false @moduledoc false
use Phoenix.Component use Phoenix.Component
import AshAdmin.CoreComponents
attr :resource, :any, required: true attr :resource, :any, required: true
attr :on_change, :string, required: true attr :on_change, :string, required: true
@ -18,7 +19,7 @@ defmodule AshAdmin.Components.Resource.SelectTable do
(is_nil(@polymorphic_actions) || @action.name in @polymorphic_actions) (is_nil(@polymorphic_actions) || @action.name in @polymorphic_actions)
}> }>
<.form :let={form} for={to_form(%{}, as: :table)} phx-change={@on_change} phx-target={@target}> <.form :let={form} for={to_form(%{}, as: :table)} phx-change={@on_change} phx-target={@target}>
<%= Phoenix.HTML.Form.select(form, :table, @tables) %> <.input type="select" field={form[:table]} options={@tables} />
</.form> </.form>
</div> </div>
</div> </div>

View file

@ -159,7 +159,7 @@ defmodule AshAdmin.MixProject do
{:phoenix_view, "~> 2.0"}, {:phoenix_view, "~> 2.0"},
{:phoenix, "~> 1.7"}, {:phoenix, "~> 1.7"},
{:phoenix_live_view, "~> 0.19"}, {:phoenix_live_view, "~> 0.19"},
{:phoenix_html, "~> 3.2"}, {:phoenix_html, "~> 3.2 or ~> 4.0"},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:tails, "~> 0.1"}, {:tails, "~> 0.1"},
{:gettext, "~> 0.20"}, {:gettext, "~> 0.20"},

View file

@ -44,7 +44,7 @@
"phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"},
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"}, "phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
"plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
"postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"},