defmodule AshHqWeb.AppViewLive do
use Surface.LiveView,
container: {:div, class: "h-full"}
alias AshHq.Docs.Extensions.RenderMarkdown
alias AshHqWeb.Components.{Search, SearchBar}
alias AshHqWeb.Pages.{Docs, Home, LogIn, Register, ResetPassword, UserSettings}
alias AshHqWeb.Router.Helpers, as: Routes
alias Phoenix.LiveView.JS
alias Surface.Components.{Link, LiveRedirect}
require Ash.Query
data(configured_theme, :string, default: :system)
data(searching, :boolean, default: false)
data(selected_versions, :map, default: %{})
data(libraries, :list, default: [])
data(selected_types, :map, default: %{})
data(sidebar_state, :map, default: %{})
data(current_user, :map)
data(library, :any, default: nil)
data(extension, :any, default: nil)
data(docs, :any, default: nil)
data(library_version, :any, default: nil)
data(guide, :any, default: nil)
data(doc_path, :list, default: [])
data(dsls, :list, default: [])
data(dsl, :any, default: nil)
data(options, :list, default: [])
data(module, :any, default: nil)
def render(assigns) do
~F"""
{#if @live_action == :docs_dsl}
{/if}
Get Started
|
|
{#if @current_user}
{#else}
Sign In
{/if}
{#for flash <- List.wrap(live_flash(@flash, :error))}
{flash}
{/for}
{#for flash <- List.wrap(live_flash(@flash, :info))}
{flash}
{/for}
{#case @live_action}
{#match :home}
{#match :docs_dsl}
{#match :user_settings}
{#match :log_in}
{#match :register}
{#match :reset_password}
{/case}
"""
end
defp toggle_account_dropdown(js \\ %JS{}) do
js
|> JS.toggle(
to: "#account-dropdown",
in: {
"transition ease-out duration-100",
"opacity-0 scale-95",
"opacity-100 scale-100"
},
out: {
"transition ease-in duration-75",
"opacity-100 scale-100",
"opacity-0 scale-05"
}
)
end
def handle_params(params, uri, socket) do
{:noreply,
socket
|> assign(params: params, uri: uri)
|> load_docs()}
end
def handle_event("collapse_sidebar", %{"id" => id}, socket) do
new_state = Map.put(socket.assigns.sidebar_state, id, "closed")
{:noreply,
socket |> assign(:sidebar_state, new_state) |> push_event("sidebar-state", new_state)}
end
def handle_event("expand_sidebar", %{"id" => id}, socket) do
new_state = Map.put(socket.assigns.sidebar_state, id, "open")
{:noreply,
socket |> assign(:sidebar_state, new_state) |> push_event("sidebar-state", new_state)}
end
def handle_event("change-versions", %{"versions" => versions}, socket) do
{:noreply,
socket
|> assign(:selected_versions, versions)
|> load_docs()
|> push_event("selected-versions", versions)}
end
def handle_event("change-types", %{"types" => types}, socket) do
types =
types
|> Enum.filter(fn {_, value} ->
value == "true"
end)
|> Enum.map(&elem(&1, 0))
{:noreply,
socket
|> assign(
:selected_types,
types
)
|> push_event("selected-types", %{types: types})}
end
def handle_event("toggle_theme", _, socket) do
theme =
case socket.assigns.configured_theme do
"light" ->
"dark"
"dark" ->
"system"
"system" ->
"light"
end
{:noreply,
socket
|> assign(:configured_theme, theme)
|> push_event("set_theme", %{theme: theme})}
end
def handle_info({:new_sidebar_state, new_state}, socket) do
{:noreply,
socket |> assign(:sidebar_state, new_state) |> push_event("sidebar-state", new_state)}
end
defp load_docs(%{assigns: %{live_action: :docs_dsl}} = socket) do
new_libraries =
socket.assigns.libraries
|> Enum.map(fn library ->
latest_version = AshHqWeb.Helpers.latest_version(library)
Map.update!(library, :versions, fn versions ->
Enum.map(versions, fn version ->
if (socket.assigns[:selected_versions][library.id] in ["latest", nil, ""] &&
latest_version &&
version.id == latest_version.id) ||
version.id == socket.assigns[:selected_versions][library.id] do
dsls_query =
AshHq.Docs.Dsl
|> Ash.Query.sort(order: :asc)
|> load_for_search(socket.assigns[:params]["dsl_path"])
options_query =
AshHq.Docs.Option
|> Ash.Query.sort(order: :asc)
|> load_for_search(socket.assigns[:params]["dsl_path"])
functions_query =
AshHq.Docs.Function
|> Ash.Query.sort(name: :asc, arity: :asc)
|> load_for_search(socket.assigns[:params]["module"])
guides_query =
AshHq.Docs.Guide
|> Ash.Query.new()
|> load_for_search(socket.assigns[:params]["guide"])
modules_query =
AshHq.Docs.Module
|> Ash.Query.sort(order: :asc)
|> Ash.Query.load(functions: functions_query)
|> load_for_search(socket.assigns[:params]["module"])
extensions_query =
AshHq.Docs.Extension
|> Ash.Query.sort(order: :asc)
|> Ash.Query.load(options: options_query, dsls: dsls_query)
|> load_for_search(socket.assigns[:params]["extension"])
AshHq.Docs.load!(version,
extensions: extensions_query,
guides: guides_query,
modules: modules_query
)
else
version
end
end)
end)
end)
socket
|> assign(:libraries, new_libraries)
|> assign_library()
|> assign_extension()
|> assign_guide()
|> assign_module()
|> assign_dsl()
|> assign_docs()
end
defp load_docs(socket), do: socket
def mount(_params, session, socket) do
configured_theme = session["theme"] || "system"
configured_library_versions =
case session["selected_versions"] do
nil ->
%{}
"" ->
%{}
value ->
value
|> String.split(",")
|> Map.new(fn str ->
str
|> String.split(":")
|> List.to_tuple()
end)
end
all_types = AshHq.Docs.Extensions.Search.Types.types()
selected_types =
case session["selected_types"] do
nil ->
AshHq.Docs.Extensions.Search.Types.types()
types ->
types
|> String.split(",")
|> Enum.filter(&(&1 in all_types))
end
sidebar_state =
case session["sidebar_state"] do
nil ->
%{}
value ->
value
|> String.split(",")
|> Map.new(fn str ->
str
|> String.split(":")
|> List.to_tuple()
end)
end
versions_query =
AshHq.Docs.LibraryVersion
|> Ash.Query.sort(version: :desc)
libraries = AshHq.Docs.Library.read!(load: [versions: versions_query])
selected_versions =
Enum.reduce(libraries, configured_library_versions, fn library, acc ->
Map.put_new(acc, library.id, "latest")
end)
{:ok,
socket
|> assign(:libraries, libraries)
|> assign(
:selected_versions,
selected_versions
)
|> assign(
:selected_types,
selected_types
)
|> assign(:selected_versions, selected_versions)
|> assign(configured_theme: configured_theme, sidebar_state: sidebar_state)
|> push_event("selected-versions", selected_versions)
|> push_event("selected_types", %{types: selected_types})}
end
def toggle_search(js \\ %JS{}) do
js
|> JS.dispatch("js:noscroll-main", to: "#search-box")
|> JS.toggle(
to: "#search-box",
in: {
"transition ease-in duration-100",
"opacity-0",
"opacity-100"
},
out: {
"transition ease-out duration-75",
"opacity-100",
"opacity-0"
}
)
|> 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",
to: "#search-box"
)
|> JS.dispatch("js:focus", to: "#search-input")
end
defp load_for_search(query, docs_for) do
query
|> Ash.Query.load(AshHq.Docs.Extensions.Search.load_for_search(query.resource))
|> deselect_doc_attributes()
|> load_docs_for(docs_for)
end
defp load_docs_for(query, nil), do: query
defp load_docs_for(query, []), do: query
defp load_docs_for(query, true) do
query.resource
|> AshHq.Docs.Extensions.RenderMarkdown.render_attributes()
|> Enum.reduce(query, fn {source, target}, query ->
Ash.Query.select(query, [source, target])
end)
end
defp load_docs_for(query, name) when is_list(name) do
Ash.Query.load(query, html_for: %{for: Enum.join(name, "/")})
end
defp load_docs_for(query, name) do
Ash.Query.load(query, html_for: %{for: name})
end
defp deselect_doc_attributes(query) do
query.resource
|> AshHq.Docs.Extensions.RenderMarkdown.render_attributes()
|> Enum.reduce(query, fn {source, target}, query ->
Ash.Query.deselect(query, [source, target])
end)
end
defp assign_guide(socket) do
guide =
cond do
socket.assigns[:params]["guide"] && socket.assigns.library_version ->
Enum.find(socket.assigns.library_version.guides, fn guide ->
guide.route == Enum.join(socket.assigns[:params]["guide"], "/")
end)
socket.assigns.library_version && socket.assigns.library_version.default_guide &&
!socket.assigns[:params]["dsl_path"] && !socket.assigns[:params]["module"] &&
!socket.assigns[:params]["extension"] ->
Enum.find(socket.assigns.library_version.guides, fn guide ->
guide.name == socket.assigns.library_version.default_guide
end)
true ->
nil
end
assign(socket, :guide, guide)
end
defp assign_dsl(socket) do
case socket.assigns[:params]["dsl_path"] do
nil ->
assign(socket, :dsl, nil)
path ->
path = Enum.join(path, "/")
dsl =
Enum.find(
socket.assigns.extension.dsls,
fn dsl ->
dsl.sanitized_path == path
end
)
new_state = Map.put(socket.assigns.sidebar_state, dsl.id, "open")
unless socket.assigns.sidebar_state[dsl.id] == "open" do
send(self(), {:new_sidebar_state, new_state})
end
socket
|> assign(
:dsl,
dsl
)
end
end
defp assign_module(socket) do
if socket.assigns.library && socket.assigns.library_version &&
socket.assigns[:params]["module"] do
module =
Enum.find(
socket.assigns.library_version.modules,
&(&1.sanitized_name == socket.assigns[:params]["module"])
)
assign(socket,
module: module
)
else
assign(socket, :module, nil)
end
end
defp assign_docs(socket) do
cond do
socket.assigns.module ->
assign(socket,
docs: socket.assigns.module.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.module.name],
options: []
)
socket.assigns.dsl ->
assign(socket,
docs: socket.assigns.dsl.html_for,
doc_path:
[
socket.assigns.library.name,
socket.assigns.extension.name
] ++ socket.assigns.dsl.path ++ [socket.assigns.dsl.name],
options:
Enum.filter(
socket.assigns.extension.options,
&(&1.path == socket.assigns.dsl.path ++ [socket.assigns.dsl.name])
)
)
socket.assigns.extension ->
assign(socket,
docs: socket.assigns.extension.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.extension.name],
options: []
)
socket.assigns.guide ->
assign(socket,
docs: socket.assigns.guide.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.guide.name],
options: []
)
true ->
assign(socket, docs: "", doc_path: [], dsls: [])
end
end
defp assign_extension(socket) do
if socket.assigns.library_version && socket.assigns[:params]["extension"] do
extensions = socket.assigns.library_version.extensions
assign(socket,
extension:
Enum.find(extensions, fn extension ->
extension.sanitized_name == socket.assigns[:params]["extension"]
end)
)
else
assign(socket, :extension, nil)
end
end
defp assign_library(socket) do
case Enum.find(
socket.assigns.libraries,
&(&1.name == socket.assigns.params["library"])
) do
nil ->
assign(socket, library: nil, library_version: nil)
library ->
socket =
if socket.assigns[:params]["version"] do
library_version =
case socket.assigns[:params]["version"] do
"latest" ->
AshHqWeb.Helpers.latest_version(library)
version ->
Enum.find(
library.versions,
&(&1.version == version)
)
end
if library_version do
socket =
assign(
socket,
library_version: library_version
)
if socket.assigns.params["version"] != "latest" &&
(!socket.assigns[:library] ||
socket.assigns.params["library"] !=
socket.assigns.library.name) do
new_selected_versions =
Map.put(socket.assigns.selected_versions, library.id, library_version.id)
socket
|> assign(selected_versions: new_selected_versions)
|> push_event("selected-versions", new_selected_versions)
else
socket
end
else
assign(socket, :library_version, nil)
end
else
assign(socket, :library_version, nil)
end
assign(socket, :library, library)
end
end
end