2022-03-26 10:17:01 +13:00
|
|
|
defmodule AshHqWeb.AppViewLive do
|
|
|
|
use Surface.LiveView,
|
|
|
|
container: {:div, class: "h-full"}
|
|
|
|
|
|
|
|
alias AshHqWeb.Components.{CalloutText, CodeExample, Search}
|
|
|
|
alias Phoenix.LiveView.JS
|
|
|
|
|
|
|
|
data configured_theme, :string, default: :system
|
|
|
|
data searching, :boolean, default: false
|
|
|
|
|
|
|
|
def render(assigns) do
|
|
|
|
~F"""
|
|
|
|
<div
|
|
|
|
id="app"
|
|
|
|
class={"h-full font-sans": true, "#{@configured_theme}": true}
|
|
|
|
phx-hook="ColorTheme"
|
|
|
|
>
|
|
|
|
<div id="main-container" class="h-full bg-white dark:bg-primary-black dark:text-silver-phoenix">
|
|
|
|
<Search id="search-box" close={close_search()} />
|
|
|
|
<div class="flex justify-between pt-4 px-4">
|
|
|
|
<div class="flex flex-row align-baseline">
|
2022-03-26 16:16:20 +13:00
|
|
|
<img class="h-10 hidden dark:block" src="/images/ash-framework-dark.png">
|
|
|
|
<img class="h-10 dark:hidden" src="/images/ash-framework-light.png">
|
2022-03-26 10:17:01 +13:00
|
|
|
</div>
|
|
|
|
<div class="flex flex-row">
|
|
|
|
<button phx-click="toggle_theme">
|
|
|
|
{#case @configured_theme}
|
|
|
|
{#match "light"}
|
2022-03-26 16:16:20 +13:00
|
|
|
<Heroicons.Outline.SunIcon class="w-10 h-10" />
|
2022-03-26 10:17:01 +13:00
|
|
|
{#match _}
|
2022-03-26 16:16:20 +13:00
|
|
|
<Heroicons.Outline.MoonIcon class="w-10 h-10" />
|
2022-03-26 10:17:01 +13:00
|
|
|
{/case}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div class="mt-4 dark:bg-primary-black dark:bg-dark-grid bg-light-grid px-12 pt-12 flex flex-col items-center">
|
|
|
|
<div class="text-5xl font-bold max-w-5xl mx-auto mt-2 text-center">
|
|
|
|
Build <CalloutText>powerful</CalloutText> and <CalloutText>extensible</CalloutText> Elixir applications with a next generation tool-chain.
|
|
|
|
</div>
|
|
|
|
<div class="text-xl font-light text-gray-700 dark:text-gray-400 max-w-4xl mx-auto mt-4 text-center">
|
|
|
|
A declarative foundation for modern applications. Use extensions like <CalloutText>Ash GraphQL</CalloutText> and <CalloutText>Ash Json Api</CalloutText> to add APIs in minutes,
|
|
|
|
or build your own extensions. Plug-and-play with other excellent tools like <CalloutText>Phoenix</CalloutText> and <CalloutText>Absinthe</CalloutText>.
|
|
|
|
</div>
|
2022-03-26 16:16:20 +13:00
|
|
|
<button
|
|
|
|
id="search-button"
|
|
|
|
class="w-96 button hide xl:block border border-gray-400 bg-gray-200 dark:border-gray-700 rounded-lg dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 mt-4"
|
|
|
|
phx-click={toggle_search()}
|
|
|
|
>
|
2022-03-26 10:17:01 +13:00
|
|
|
<div class="flex flex-row justify-between items-center px-4">
|
|
|
|
<div class="h-12 flex flex-row justify-start items-center text-center space-x-4">
|
|
|
|
<Heroicons.Outline.SearchIcon class="w-4 h-4" />
|
|
|
|
<div>Search Documentation</div>
|
|
|
|
</div>
|
|
|
|
<div>⌘ K</div>
|
|
|
|
</div>
|
|
|
|
</button>
|
|
|
|
<div class="pt-6 pb-6 w-full hidden md:block">
|
|
|
|
<div class="flex flex-row justify-center space-x-24 xl:space-x-32">
|
2022-03-26 16:16:20 +13:00
|
|
|
<CodeExample
|
|
|
|
id="define-a-resource"
|
|
|
|
class="grow min-w-fit"
|
|
|
|
text={post_example()}
|
|
|
|
title="Define a resource"
|
|
|
|
/>
|
2022-03-26 10:17:01 +13:00
|
|
|
<div class="flex flex-col space-y-8">
|
2022-03-26 16:16:20 +13:00
|
|
|
<CodeExample
|
|
|
|
class="w-auto"
|
|
|
|
collapsable
|
|
|
|
id="use-it-programatically"
|
|
|
|
text={changeset_example()}
|
|
|
|
title="Use it programatically"
|
|
|
|
/>
|
|
|
|
<CodeExample
|
|
|
|
class="w-auto"
|
|
|
|
collapsable
|
|
|
|
id="graphql-interface"
|
|
|
|
text={graphql_example()}
|
|
|
|
title="Add a GraphQL interface"
|
|
|
|
/>
|
|
|
|
<CodeExample
|
|
|
|
class="w-auto"
|
|
|
|
collapsable
|
|
|
|
start_collapsed
|
|
|
|
id="authorization-policies"
|
|
|
|
text={policies_example()}
|
|
|
|
title="Add authorization policies"
|
|
|
|
/>
|
|
|
|
<CodeExample
|
|
|
|
class="w-auto"
|
|
|
|
collapsable
|
|
|
|
start_collapsed
|
|
|
|
id="aggregates"
|
|
|
|
text={aggregate_example()}
|
|
|
|
title="Define aggregates and calculations"
|
|
|
|
/>
|
|
|
|
<CodeExample
|
|
|
|
class="w-auto"
|
|
|
|
collapsable
|
|
|
|
start_collapsed
|
|
|
|
id="pubsub"
|
|
|
|
text={notifier_example()}
|
|
|
|
title="Broadcast changes over Phoenix PubSub"
|
|
|
|
/>
|
|
|
|
<CodeExample
|
|
|
|
class="w-auto"
|
|
|
|
collapsable
|
|
|
|
start_collapsed
|
|
|
|
id="live-view"
|
|
|
|
text={live_view_example()}
|
|
|
|
title="Use it with Phoenix LiveView"
|
|
|
|
/>
|
2022-03-26 10:17:01 +13:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-03-26 16:16:20 +13:00
|
|
|
</div>
|
2022-03-26 10:17:01 +13:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="bg-primary-black">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
def mount(_params, session, socket) do
|
|
|
|
configured_theme = session["theme"] || "dark"
|
|
|
|
|
|
|
|
{:ok, assign(socket, configured_theme: configured_theme)}
|
|
|
|
end
|
|
|
|
|
|
|
|
def toggle_search(js \\ %JS{}) do
|
|
|
|
js
|
|
|
|
|> JS.dispatch("js:noscroll-main", to: "#search-box")
|
|
|
|
|> JS.toggle(
|
|
|
|
in: "fade-in duration-100",
|
|
|
|
out: "fade-out duration-100",
|
|
|
|
to: "#search-box",
|
|
|
|
time: 100
|
|
|
|
)
|
|
|
|
|> JS.dispatch("js:focus", to: "#search-input")
|
|
|
|
end
|
|
|
|
|
|
|
|
def close_search(js \\ %JS{}) do
|
|
|
|
js
|
|
|
|
|> JS.dispatch("js:noscroll-main", to: "#search-box")
|
|
|
|
|> JS.hide(
|
|
|
|
transition: "fade-out duration-100",
|
|
|
|
to: "#search-box",
|
|
|
|
time: 100
|
|
|
|
)
|
|
|
|
|> JS.dispatch("js:focus", to: "#search-input")
|
|
|
|
end
|
|
|
|
|
|
|
|
def handle_event("toggle_theme", _, socket) do
|
|
|
|
theme =
|
|
|
|
case socket.assigns.configured_theme do
|
|
|
|
"light" ->
|
|
|
|
"dark"
|
|
|
|
|
|
|
|
"dark" ->
|
|
|
|
"light"
|
|
|
|
end
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
socket
|
|
|
|
|> assign(:configured_theme, theme)
|
|
|
|
|> push_event("set_theme", %{theme: theme})}
|
|
|
|
end
|
|
|
|
|
|
|
|
defp changeset_example() do
|
|
|
|
"""
|
|
|
|
post = Example.Post.create!(%{
|
|
|
|
text: "Declarative programming is fun!"
|
|
|
|
})
|
|
|
|
|
|
|
|
Example.Post.react!(post, %{type: :like})
|
|
|
|
|
|
|
|
Example.Post
|
|
|
|
|> Ash.Query.filter(likes > 10)
|
|
|
|
|> Ash.Query.sort(likes: :desc)
|
|
|
|
|> Example.read!()
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp live_view_example() do
|
|
|
|
"""
|
|
|
|
def mount(_params, _session, socket) do
|
|
|
|
form = AshPhoenix.Form.for_create(Example.Post, :create)
|
|
|
|
|
|
|
|
{:ok, assign(socket, :form, form}}
|
|
|
|
end
|
|
|
|
|
|
|
|
def handle_event("validate", %{"form" => input}, socket) do
|
|
|
|
form = AshPhoenix.Form.validate(socket.assigns.form, input)
|
|
|
|
|
|
|
|
{:ok, assign(socket, :form, form)}
|
|
|
|
end
|
|
|
|
|
|
|
|
def handle_event("submit", _, socket) do
|
|
|
|
case AshPhoenix.Form.submit(socket.assigns.form) do
|
|
|
|
{:ok, post} ->
|
|
|
|
{:ok, redirect_to_post(socket, post)}
|
|
|
|
|
|
|
|
{:error, form_with_errors} ->
|
|
|
|
{:noreply, assign(socket, :form, form_with_errors)}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp graphql_example() do
|
|
|
|
"""
|
|
|
|
graphql do
|
|
|
|
type :post
|
|
|
|
|
|
|
|
queries do
|
|
|
|
get :get_post, :read
|
|
|
|
list :feed, :read
|
|
|
|
end
|
|
|
|
|
|
|
|
mutations do
|
|
|
|
create :create_post, :create
|
|
|
|
update :react_to_post, :react
|
|
|
|
end
|
|
|
|
end
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp policies_example() do
|
|
|
|
"""
|
|
|
|
policies do
|
|
|
|
policy action_type(:read) do
|
|
|
|
authorize_if expr(visibility == :everyone)
|
|
|
|
authorize_if relates_to_actor_via([:author, :friends])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp notifier_example() do
|
|
|
|
"""
|
|
|
|
pub_sub do
|
|
|
|
module ExampleEndpoint
|
|
|
|
prefix "post"
|
|
|
|
|
|
|
|
publish_all :create, ["created"]
|
|
|
|
publish :react, ["reaction", :id] event: "reaction"
|
|
|
|
end
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp aggregate_example() do
|
|
|
|
"""
|
|
|
|
aggregates do
|
|
|
|
count :likes, :reactions do
|
|
|
|
filter expr(type == :like)
|
|
|
|
end
|
|
|
|
|
|
|
|
count :dislikes, :reactions do
|
|
|
|
filter expr(type == :dislike)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
calculations do
|
|
|
|
calculate :like_ratio, :float do
|
|
|
|
expr(likes / likes + dislikes)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp post_example() do
|
|
|
|
"""
|
|
|
|
defmodule Example.Post do
|
|
|
|
use AshHq.Resource,
|
|
|
|
data_layer: AshPostgres.DataLayer
|
|
|
|
|
|
|
|
postgres do
|
|
|
|
table "posts"
|
|
|
|
repo Example.Repo
|
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
attribute :text, :string do
|
|
|
|
allow_nil? false
|
|
|
|
end
|
|
|
|
|
|
|
|
attribute :visibility, :atom do
|
|
|
|
constraints [
|
|
|
|
one_of: [:friends, :everyone]
|
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
|
|
|
update :react do
|
|
|
|
argument :type, Example.Types.ReactionType do
|
|
|
|
allow_nil? false
|
|
|
|
end
|
|
|
|
|
|
|
|
change manage_relationship(
|
|
|
|
:type,
|
|
|
|
:reactions,
|
|
|
|
type: :append
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
|
|
|
belongs_to :author, Example.User do
|
|
|
|
required? true
|
|
|
|
end
|
|
|
|
|
|
|
|
has_many :reactions, Example.Reaction
|
|
|
|
end
|
|
|
|
end
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
end
|