205 lines
6.0 KiB
Elixir
205 lines
6.0 KiB
Elixir
|
defmodule MemexWeb.CoreComponents do
|
||
|
@moduledoc """
|
||
|
Provides core UI components.
|
||
|
"""
|
||
|
use Phoenix.Component
|
||
|
import MemexWeb.{Gettext, ViewHelpers}
|
||
|
alias Memex.{Accounts, Accounts.Invite, Accounts.Invites, Accounts.User}
|
||
|
alias Memex.Contexts.Context
|
||
|
alias Memex.Notes.Note
|
||
|
alias Memex.Pipelines.Steps.Step
|
||
|
alias MemexWeb.{Endpoint, HomeLive}
|
||
|
alias MemexWeb.Router.Helpers, as: Routes
|
||
|
alias Phoenix.HTML
|
||
|
alias Phoenix.LiveView.JS
|
||
|
|
||
|
embed_templates("core_components/*")
|
||
|
|
||
|
attr :title_content, :string, default: nil
|
||
|
attr :current_user, User, default: nil
|
||
|
|
||
|
def topbar(assigns)
|
||
|
|
||
|
attr :return_to, :string, required: true
|
||
|
slot(:inner_block)
|
||
|
|
||
|
@doc """
|
||
|
Renders a live component inside a modal.
|
||
|
|
||
|
The rendered modal receives a `:return_to` option to properly update
|
||
|
the URL when the modal is closed.
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
<.modal return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}>
|
||
|
<.live_component
|
||
|
module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent}
|
||
|
id={@<%= schema.singular %>.id || :new}
|
||
|
title={@page_title}
|
||
|
action={@live_action}
|
||
|
return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}
|
||
|
<%= schema.singular %>: @<%= schema.singular %>
|
||
|
/>
|
||
|
</.modal>
|
||
|
"""
|
||
|
def modal(assigns)
|
||
|
|
||
|
defp hide_modal(js \\ %JS{}) do
|
||
|
js
|
||
|
|> JS.hide(to: "#modal", transition: "fade-out")
|
||
|
|> JS.hide(to: "#modal-bg", transition: "fade-out")
|
||
|
|> JS.hide(to: "#modal-content", transition: "fade-out-scale")
|
||
|
end
|
||
|
|
||
|
attr :action, :string, required: true
|
||
|
attr :value, :boolean, required: true
|
||
|
attr :id, :string, default: nil
|
||
|
slot(:inner_block)
|
||
|
|
||
|
@doc """
|
||
|
A toggle button element that can be directed to a liveview or a
|
||
|
live_component's `handle_event/3`.
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
<.toggle_button action="my_liveview_action" value={@some_value}>
|
||
|
<span>Toggle me!</span>
|
||
|
</.toggle_button>
|
||
|
<.toggle_button action="my_live_component_action" target={@myself} value={@some_value}>
|
||
|
<span>Whatever you want</span>
|
||
|
</.toggle_button>
|
||
|
"""
|
||
|
def toggle_button(assigns)
|
||
|
|
||
|
attr :user, User, required: true
|
||
|
slot(:inner_block, required: true)
|
||
|
|
||
|
def user_card(assigns)
|
||
|
|
||
|
attr :invite, Invite, required: true
|
||
|
attr :current_user, User, required: true
|
||
|
slot(:inner_block)
|
||
|
slot(:code_actions)
|
||
|
|
||
|
def invite_card(%{invite: invite, current_user: current_user} = assigns) do
|
||
|
assigns = assigns |> assign(:use_count, Invites.get_use_count(invite, current_user))
|
||
|
|
||
|
~H"""
|
||
|
<div class="px-8 py-4 flex flex-col justify-center items-center space-y-4
|
||
|
bg-primary-900
|
||
|
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
|
||
|
transition-all duration-300 ease-in-out">
|
||
|
<h1 class="title text-xl">
|
||
|
<%= @invite.name %>
|
||
|
</h1>
|
||
|
|
||
|
<%= if @invite.disabled_at |> is_nil() do %>
|
||
|
<h2 class="title text-md">
|
||
|
<%= if @invite.uses_left do %>
|
||
|
<%= gettext(
|
||
|
"uses left: %{uses_left_count}",
|
||
|
uses_left_count: @invite.uses_left
|
||
|
) %>
|
||
|
<% else %>
|
||
|
<%= gettext("uses left: unlimited") %>
|
||
|
<% end %>
|
||
|
</h2>
|
||
|
<% else %>
|
||
|
<h2 class="title text-md">
|
||
|
<%= gettext("invite disabled") %>
|
||
|
</h2>
|
||
|
<% end %>
|
||
|
|
||
|
<.qr_code
|
||
|
content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)}
|
||
|
filename={@invite.name}
|
||
|
/>
|
||
|
|
||
|
<h2 :if={@use_count != 0} class="title text-md">
|
||
|
<%= gettext("uses: %{uses_count}", uses_count: @use_count) %>
|
||
|
</h2>
|
||
|
|
||
|
<div class="flex flex-row flex-wrap justify-center items-center">
|
||
|
<code
|
||
|
id={"code-#{@invite.id}"}
|
||
|
class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all
|
||
|
text-primary-400 bg-primary-800"
|
||
|
phx-no-format
|
||
|
><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code>
|
||
|
<%= if @code_actions, do: render_slot(@code_actions) %>
|
||
|
</div>
|
||
|
|
||
|
<div :if={@inner_block} class="flex space-x-4 justify-center items-center">
|
||
|
<%= render_slot(@inner_block) %>
|
||
|
</div>
|
||
|
</div>
|
||
|
"""
|
||
|
end
|
||
|
|
||
|
attr :note, Note, required: true
|
||
|
|
||
|
def note_content(assigns) do
|
||
|
~H"""
|
||
|
<div
|
||
|
id={"show-note-content-#{@note.id}"}
|
||
|
class="input input-primary h-128 min-h-128 inline-block whitespace-pre-wrap overflow-x-hidden overflow-y-auto"
|
||
|
phx-hook="MaintainAttrs"
|
||
|
phx-update="ignore"
|
||
|
readonly
|
||
|
phx-no-format
|
||
|
><p class="inline"><%= add_links_to_content(@note.content, "note-link") %></p></div>
|
||
|
"""
|
||
|
end
|
||
|
|
||
|
attr :context, Context, required: true
|
||
|
|
||
|
def context_content(assigns) do
|
||
|
~H"""
|
||
|
<div
|
||
|
id={"show-context-content-#{@context.id}"}
|
||
|
class="input input-primary h-128 min-h-128 inline-block whitespace-pre-wrap overflow-x-hidden overflow-y-auto"
|
||
|
phx-hook="MaintainAttrs"
|
||
|
phx-update="ignore"
|
||
|
readonly
|
||
|
phx-no-format
|
||
|
><p class="inline"><%= add_links_to_content(@context.content, "context-note") %></p></div>
|
||
|
"""
|
||
|
end
|
||
|
|
||
|
attr :step, Step, required: true
|
||
|
|
||
|
def step_content(assigns) do
|
||
|
~H"""
|
||
|
<div
|
||
|
id={"show-step-content-#{@step.id}"}
|
||
|
class="input input-primary h-32 min-h-32 inline-block whitespace-pre-wrap overflow-x-hidden overflow-y-auto"
|
||
|
phx-hook="MaintainAttrs"
|
||
|
phx-update="ignore"
|
||
|
readonly
|
||
|
phx-no-format
|
||
|
><p class="inline"><%= add_links_to_content(@step.content, "step-context") %></p></div>
|
||
|
"""
|
||
|
end
|
||
|
|
||
|
defp add_links_to_content(content, data_qa_prefix) do
|
||
|
Regex.replace(
|
||
|
~r/\[\[([\p{L}\p{N}\-]+)\]\]/,
|
||
|
content,
|
||
|
fn _whole_match, slug ->
|
||
|
link =
|
||
|
HTML.Link.link(
|
||
|
"[[#{slug}]]",
|
||
|
to: Routes.note_show_path(Endpoint, :show, slug),
|
||
|
class: "link inline",
|
||
|
data: [qa: "#{data_qa_prefix}-#{slug}"]
|
||
|
)
|
||
|
|> HTML.Safe.to_iodata()
|
||
|
|> IO.iodata_to_binary()
|
||
|
|
||
|
"</p>#{link}<p class=\"inline\">"
|
||
|
end
|
||
|
)
|
||
|
|> HTML.raw()
|
||
|
end
|
||
|
end
|