ash_hq/lib/ash_hq_web/views/app_view_live.ex
Rebecca Le 242771dca9
fix: Rewrite theme setting to properly support system theme
The existing code was a bit convoluted, and did not work correctly
when toggling the theme back to the system theme - neither theme was
properly applied on page load (cookie was present, so no theming was added)

This also fixes the edge case where changing the system theme would
override the site theme, due to not reading/using the cookie
value correctly
2023-10-03 12:11:07 +08:00

299 lines
9.4 KiB
Elixir

defmodule AshHqWeb.AppViewLive do
# credo:disable-for-this-file Credo.Check.Readability.MaxLineLength
use Surface.LiveView,
container: {:div, class: "h-full"}
alias AshHqWeb.Components.AppView.TopBar
alias AshHqWeb.Components.Search
alias AshHqWeb.Pages.{Ashley, Blog, Community, Docs, Home, Media, UserSettings}
alias Phoenix.LiveView.JS
alias Surface.Components.Context
require Ash.Query
import AshHqWeb.Tails
data(configured_theme, :string, default: :system)
data(libraries, :list, default: [])
data(selected_types, :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(%{platform: :ios} = assigns) do
~F"""
{#case @live_action}
{#match :home}
<Home id="home" />
{/case}
"""
end
def render(assigns) do
~F"""
<div
id="app"
class={classes(["h-full font-sans": true])}
phx-hook="ColorTheme"
>
<head>
<meta property="og:type" content="text/html">
<meta property="og:image" content="https://ash-hq.org/images/ash-logo-side.png">
<meta property="og:url" content={to_string(@uri)}>
<meta property="og:site_name" content="Ash HQ">
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:domain" content="ash-hq.org">
<meta property="twitter:site" content="@AshFramework">
<!-- Need to adjust this for future blog writers -->
<meta property="twitter:creator" content="@ZachSDaniel1">
{#if @live_action not in [:docs_dsl, :blog, :forum]}
<meta property="og:title" content="Ash Framework">
<meta
property="og:description"
content="A declarative foundation for ambitious Elixir applications. Model your domain, derive the rest."
/>
{/if}
</head>
<Search
id="search-box"
uri={@uri}
close={close_search()}
libraries={@libraries}
selected_types={@selected_types}
change_types="change-types"
change_versions="change-versions"
remove_version="remove_version"
/>
<button id="search-button" class="hidden" phx-click={AshHqWeb.AppViewLive.toggle_search()} />
<div
id="main-container"
class={
"w-full min-h-screen bg-white dark:bg-base-dark-850 dark:text-white flex flex-col items-stretch",
"h-screen overflow-y-auto": @live_action != :docs_dsl
}
>
<TopBar
live_action={@live_action}
toggle_theme="toggle_theme"
configured_theme={@configured_theme}
current_user={@current_user}
/>
{#for flash <- List.wrap(live_flash(@flash, :error))}
<p class="alert alert-warning" role="alert">{flash}</p>
{/for}
{#for flash <- List.wrap(live_flash(@flash, :info))}
<p class="alert alert-info max-h-min" role="alert">{flash}</p>
{/for}
{#case @live_action}
{#match :home}
<Home id="home" device_brand={@device_brand} />
{#match :blog}
<Blog id="blog" params={@params} />
{#match :community}
<Community id="community" />
{#match :docs_dsl}
<Docs
id="docs"
uri={@uri}
params={@params}
remove_version="remove_version"
change_versions="change-versions"
libraries={@libraries}
/>
{#match :user_settings}
<UserSettings id="user_settings" current_user={@current_user} />
{#match :media}
<Media id="media" />
{#match :ashley}
<Ashley id="ashley" current_user={@current_user} params={@params} />
{/case}
{#if @live_action not in [:docs_dsl, :ashley]}
<footer class="p-8 sm:p-6 bg-base-light-200 dark:bg-base-dark-850 sm:justify-center sticky">
<div class="md:flex md:justify-around">
<div class="flex justify-center mb-6 md:mb-0">
<a href="/" class="flex items-center">
<img src="/images/ash-logo-side.svg" class="mr-3 h-32" alt="Ash Framework Logo">
</a>
</div>
<div class="grid grid-cols-3 gap-8 sm:gap-6">
<div>
<h2 class="mb-6 text-sm font-semibold text-gray-900 uppercase dark:text-white">Resources</h2>
<ul class="text-gray-600 dark:text-gray-400">
<li class="mb-4">
<a href="https://github.com/ash-project" class="hover:underline">Source</a>
</li>
<li class="mb-4">
<a href="/docs/guides/ash/latest/tutorials/get-started" class="hover:underline">Get Started</a>
</li>
<li class="mb-4">
<a href="/blog" class="hover:underline">Blog</a>
</li>
<li>
<a href="/media" class="hover:underline">Media</a>
</li>
</ul>
</div>
<div>
<h2 class="mb-6 text-sm font-semibold text-gray-900 uppercase dark:text-white">Community</h2>
<ul class="text-gray-600 dark:text-gray-400">
<li class="mb-4">
<a href="https://twitter.com/AshFramework" class="hover:underline">Twitter</a>
</li>
<li>
<a href="https://discord.gg/D7FNG2q" class="hover:underline">Discord</a>
</li>
</ul>
</div>
<div>
<h2 class="mb-6 text-sm font-semibold text-gray-900 uppercase dark:text-white">Help Us</h2>
<ul class="text-gray-600 dark:text-gray-400">
<li class="mb-4">
<a href="https://github.com/ash-project/ash_hq/issues/new/choose" class="hover:underline">Report an issue</a>
</li>
<li class="mb-4">
<a href="https://ash-hq.appsignal-status.com" class="hover:underline">Status Page</a>
</li>
<li>
<a href="/docs/guides/ash/latest/how_to/contribute" class="hover:underline">Contribute</a>
</li>
</ul>
</div>
</div>
</div>
</footer>
{/if}
</div>
</div>
"""
end
def handle_params(params, uri, socket) do
{:noreply,
socket
|> assign(params: params, uri: uri)}
end
def handle_info({:page_title, title}, socket) do
{:noreply, assign(socket, :page_title, "Ash Framework - #{title}")}
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 mount(_params, session, socket) do
socket = assign(socket, :page_title, "Ash Framework")
socket =
assign_new(socket, :user_agent, fn _assigns ->
get_connect_params(socket)["user_agent"]
end)
socket = assign(socket, :device_brand, "Apple")
socket = Context.put(socket, platform: socket.assigns.platform)
configured_theme = session["theme"] || "system"
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
versions_query =
AshHq.Docs.LibraryVersion
|> Ash.Query.sort(version: :desc)
libraries = AshHq.Docs.Library.read!(load: [versions: versions_query])
{:ok,
socket
|> assign(:libraries, libraries)
|> assign(
:selected_types,
selected_types
)
|> assign(configured_theme: configured_theme)
|> push_event("selected_types", %{types: selected_types})}
end
def toggle_search(js \\ %JS{}) do
js
|> 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.hide(
transition: "fade-out",
to: "#search-box"
)
|> JS.hide(transition: "fade-out", to: "#search-versions")
|> JS.show(transition: "fade-in", to: "#search-body")
end
end