From 55408f6f388bdc766ee16abfb369ea80ec80d646 Mon Sep 17 00:00:00 2001 From: ks0m1c_dharma Date: Sat, 27 Jul 2024 23:39:58 +0800 Subject: [PATCH] Leggoooo drafting matrix --- assets/js/hooks/hoverune.js | 7 +- lib/vyasa/adapters/binding.ex | 17 +- lib/vyasa/draft.ex | 131 ++++++++++++ lib/vyasa/sangh.ex | 4 +- lib/vyasa/sangh/comments.ex | 8 +- lib/vyasa/sangh/mark.ex | 30 +++ lib/vyasa_web/components/draft_matrix.ex | 43 ++++ .../components/layouts/app.html.heex | 2 +- .../live/source_live/chapter/index.ex | 197 +++++++++++------- .../live/source_live/chapter/index.html.heex | 8 +- 10 files changed, 360 insertions(+), 87 deletions(-) create mode 100644 lib/vyasa/draft.ex create mode 100644 lib/vyasa/sangh/mark.ex create mode 100644 lib/vyasa_web/components/draft_matrix.ex diff --git a/assets/js/hooks/hoverune.js b/assets/js/hooks/hoverune.js index 4856764f..439062f3 100644 --- a/assets/js/hooks/hoverune.js +++ b/assets/js/hooks/hoverune.js @@ -9,8 +9,6 @@ const findParent = (el, attr, stopper) => { } function floatHoveRune({clientX, clientY}) { - - console.log("sting like a bees") const selection = window.getSelection() var getSelectRect = selection.getRangeAt(0).getBoundingClientRect() const virtualEl = { @@ -51,7 +49,7 @@ const forgeBinding = (el, attrs) => attrs.reduce((acc, attr) => { export default HoveRune = { mounted() { const t = this.el - const targetEvents = ['click', 'pointermove', 'pointerdown'] + const targetEvents = ['pointerdown', 'pointerup'] targetEvents.forEach(e => window.addEventListener(e, ({ target }) => { var selection = window.getSelection() var getSelectRect = selection.getRangeAt(0).getBoundingClientRect(); @@ -63,8 +61,11 @@ export default HoveRune = { if (isNode) { binding = forgeBinding(target, ["node", "node_id", "field", "verse_id"]) binding["selection"] = getSelectText + this.pushEvent("bindHoveRune", {"binding": binding}) + console.log(binding) + computePosition(target, hoverune, {placement: 'top-end', middleware: [inline(getSelectRect.x, getSelectRect.y), offset(5)]}).then(({x, y}) => { hoverune.classList.remove("hidden") diff --git a/lib/vyasa/adapters/binding.ex b/lib/vyasa/adapters/binding.ex index d3a1d31d..3608b76b 100644 --- a/lib/vyasa/adapters/binding.ex +++ b/lib/vyasa/adapters/binding.ex @@ -12,13 +12,16 @@ defmodule Vyasa.Adapters.Binding do @primary_key {:id, Ecto.UUID, autogenerate: true} schema "bindings" do field :w_type, Ecto.Enum, values: [:quote, :timestamp, :null] - field :field_key, :string + + field :field_key, {:array, :string} + + field :node_id, :string, virtual: true + belongs_to :verse, Verse, foreign_key: :verse_id, type: :binary_id belongs_to :chapter, Chapter, type: :integer, references: :no, foreign_key: :chapter_no belongs_to :source, Source, foreign_key: :source_id, type: :binary_id belongs_to :translation, Translation, foreign_key: :translation_id, type: :binary_id belongs_to :comment, Comment, foreign_key: :comment_id, type: :binary_id - belongs_to :comment_bind, Comment, foreign_key: :comment_bind_id, type: :binary_id embeds_one :window, Window, on_replace: :delete do field(:line_number, :integer) @@ -37,7 +40,7 @@ defmodule Vyasa.Adapters.Binding do end - def bind_comment_changeset(%__MODULE__{} = binding, attrs) do + def windowing_changeset(%__MODULE__{} = binding, attrs) do binding |> cast(attrs, [:w_type, :verse_id, :chapter_no, :source_id]) |> typed_window_switch(attrs) @@ -98,4 +101,12 @@ defmodule Vyasa.Adapters.Binding do end end) end + + def field_lookup(%Verse{}), do: :verse_id + def field_lookup(%Chapter{}), do: :chapter_no + def field_lookup(%Source{}), do: :source_id + def field_lookup(%Translation{}), do: :translation_id + def field_lookup(%Comment{}), do: :comment_id + + def field_lookup(_), do: nil end diff --git a/lib/vyasa/draft.ex b/lib/vyasa/draft.ex new file mode 100644 index 00000000..fc740c38 --- /dev/null +++ b/lib/vyasa/draft.ex @@ -0,0 +1,131 @@ +defmodule Vyasa.Draft do + @moduledoc """ + The Drafting Context for all your marking and binding needs + """ + + import Ecto.Query, warn: false + alias Vyasa.Adapters.Binding + alias Vyasa.Sangh.Mark + alias Vyasa.Sangh + alias Vyasa.Repo + + + + def bind_node(%{"selection" => ""} = node) do + bind_node(node, %Binding{}) + end + + def bind_node(%{"selection" => selection} = node) do + bind_node(Map.delete(node, "selection"), %Binding{:window => %{:quote => selection}}) + end + + def bind_node(%{"field" => field} = node, bind) do + bind_node(Map.delete(node, "field"), %{bind | field_key: String.split(field, "::") |> Enum.map(&(String.to_existing_atom(&1)))}) + end + + def bind_node(%{"node" => node, "node_id" => node_id}, %Binding{} = bind) do + n = node + |> String.to_existing_atom() + |> struct() + |> Binding.field_lookup() + + %{bind | n => node_id, :node_id => node_id} + end + + def create_comment([%Mark{} | _]= marks) do + Sangh.create_comment(%{marks: marks}) + end + @doc """ + Returns the list of marks. + + ## Examples + + iex> list_marks() + [%Mark{}, ...] + + """ + def list_marks do + Repo.all(Mark) + end + + @doc """ + Gets a single mark. + + Raises `Ecto.NoResultsError` if the Mark does not exist. + + ## Examples + + iex> get_mark!(123) + %Mark{} + + iex> get_mark!(456) + ** (Ecto.NoResultsError) + + """ + def get_mark!(id), do: Repo.get!(Mark, id) + + @doc """ + Creates a mark. + + ## Examples + + iex> create_mark(%{field: value}) + {:ok, %Mark{}} + + iex> create_mark(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_mark(attrs \\ %{}) do + %Mark{} + |> Mark.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a mark. + + ## Examples + + iex> update_mark(mark, %{field: new_value}) + {:ok, %Mark{}} + + iex> update_mark(mark, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_mark(%Mark{} = mark, attrs) do + mark + |> Mark.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a mark. + + ## Examples + + iex> delete_mark(mark) + {:ok, %Mark{}} + + iex> delete_mark(mark) + {:error, %Ecto.Changeset{}} + + """ + def delete_mark(%Mark{} = mark) do + Repo.delete(mark) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking mark changes. + + ## Examples + + iex> change_mark(mark) + %Ecto.Changeset{data: %Mark{}} + + """ + def change_mark(%Mark{} = mark, attrs \\ %{}) do + Mark.changeset(mark, attrs) + end +end diff --git a/lib/vyasa/sangh.ex b/lib/vyasa/sangh.ex index b8b37295..057d6231 100644 --- a/lib/vyasa/sangh.ex +++ b/lib/vyasa/sangh.ex @@ -30,10 +30,10 @@ defmodule Vyasa.Sangh do ## Examples - iex> create_comment(comment, %{field: new_value}) + iex> create_comment(%{field: new_value}) {:ok, %Comment{}} - iex> create_comment(comment, %{field: bad_value}) + iex> create_comment(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ diff --git a/lib/vyasa/sangh/comments.ex b/lib/vyasa/sangh/comments.ex index c13ad782..c12c6ee7 100644 --- a/lib/vyasa/sangh/comments.ex +++ b/lib/vyasa/sangh/comments.ex @@ -2,8 +2,7 @@ defmodule Vyasa.Sangh.Comment do use Ecto.Schema import Ecto.Changeset alias EctoLtree.LabelTree, as: Ltree - alias Vyasa.Sangh.{Comment, Session} - alias Vyasa.Adapters.Binding + alias Vyasa.Sangh.{Comment, Session, Mark} @primary_key {:id, Ecto.UUID, autogenerate: false} schema "comments" do @@ -16,7 +15,8 @@ defmodule Vyasa.Sangh.Comment do belongs_to :session, Session, references: :id, type: Ecto.UUID belongs_to :parent, Comment, references: :id, type: Ecto.UUID - has_many :bindings, Binding, references: :id, foreign_key: :comment_bind_id, on_replace: :delete_if_exists + #has_many :marks, Mark, references: :id, foreign_key: :comment_bind_id, on_replace: :delete_if_exists + #has_many :bindings, Binding, references: :id, foreign_key: :comment_bind_id, on_replace: :delete_if_exists timestamps() end @@ -25,7 +25,7 @@ defmodule Vyasa.Sangh.Comment do def changeset(%Comment{} = comment, attrs) do comment |> cast(attrs, [:id, :body, :path, :chapter_number, :p_type, :session_id, :parent_id, :text_id]) - |> cast_assoc(:bindings, with: &Binding.bind_comment_changeset/2) + |> cast_assoc(:bindings, with: &Mark.changeset/2) |> validate_required([:id, :session_id]) end diff --git a/lib/vyasa/sangh/mark.ex b/lib/vyasa/sangh/mark.ex new file mode 100644 index 00000000..4ed68af8 --- /dev/null +++ b/lib/vyasa/sangh/mark.ex @@ -0,0 +1,30 @@ +defmodule Vyasa.Sangh.Mark do + @moduledoc """ + Interpretation & bounding of binding + """ + + use Ecto.Schema + import Ecto.Changeset + + alias Vyasa.Sangh.{Comment} + alias Vyasa.Adapters.Binding + + @primary_key {:id, Ecto.UUID, autogenerate: true} + schema "marks" do + field :body, :string + field :order, :integer + field :state, Ecto.Enum, values: [:draft, :bookmark, :live] + field :verse_id, :string, virtual: true + + belongs_to :comment, Comment, foreign_key: :comment_id, type: :binary_id + belongs_to :binding, Binding, foreign_key: :binding_id, type: :binary_id + + timestamps() + end + + def changeset(event, attrs) do + event + |> cast(attrs, [:body, :order, :status, :comment_id, :binding_id]) + end + +end diff --git a/lib/vyasa_web/components/draft_matrix.ex b/lib/vyasa_web/components/draft_matrix.ex new file mode 100644 index 00000000..2381065d --- /dev/null +++ b/lib/vyasa_web/components/draft_matrix.ex @@ -0,0 +1,43 @@ +defmodule VyasaWeb.DraftMatrix do + use VyasaWeb, :live_component + + @impl true + def update(assigns, socket) do + assigns = Enum.reject(assigns, fn {_k, v} -> is_nil(v) or v == [] end) + + {:ok, + socket + |> assign(assigns) + |> assign_new(:actions, fn -> [] end) + |> assign_new(:marks, fn -> [] end) + |> assign_new(:custom_style, fn -> [] end)} + end + + @impl true + def handle_event("adjust", adjusted, socket) do + style = Enum.map(adjusted, fn {key, val} -> + "#{key}: #{val}" + end) + + {:noreply, assign(socket, custom_style: style)} + end + + @doc """ + Renders a marginote w action slots for interaction with marginote + """ + @impl true + def render(assigns) do + ~H""" +
List.flatten() |> Enum.join(";")}> + <.form for={%{}} phx-submit="create_mark"> + + +
+ """ + end +end diff --git a/lib/vyasa_web/components/layouts/app.html.heex b/lib/vyasa_web/components/layouts/app.html.heex index a72d90c8..e3829211 100644 --- a/lib/vyasa_web/components/layouts/app.html.heex +++ b/lib/vyasa_web/components/layouts/app.html.heex @@ -2,7 +2,7 @@
- +

v<%= Application.spec(:vyasa, :vsn) %> diff --git a/lib/vyasa_web/live/source_live/chapter/index.ex b/lib/vyasa_web/live/source_live/chapter/index.ex index 56c2547e..71fbb950 100644 --- a/lib/vyasa_web/live/source_live/chapter/index.ex +++ b/lib/vyasa_web/live/source_live/chapter/index.ex @@ -1,11 +1,10 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do use VyasaWeb, :live_view - alias Vyasa.Written + alias Vyasa.{Written, Medium, Draft} alias Vyasa.Written.{Source, Chapter} - alias Vyasa.Medium alias VyasaWeb.OgImageController alias Utils.Struct - alias Vyasa.Sangh.Comment + alias Vyasa.Sangh.{Comment, Mark} @default_lang "en" @default_voice_lang "sa" @@ -45,6 +44,7 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do |> assign(:kv_verses, Enum.into(verses, %{}, &({&1.id, %{&1 | comments: [%Comment{signature: "Pope", body: "Achilles’ wrath, to Greece the direful spring Of woes unnumber’d, heavenly goddess, sing"}] } }))) |> assign(:src, source) + |> assign(:marks, [%Mark{state: :draft, order: 0}]) |> assign(:lang, @default_lang) |> assign(:chap, chap) |> assign(:selected_transl, ts) @@ -77,26 +77,76 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do @impl true def handle_event("bindHoveRune", - %{ - "binding" => %{ - "field" => field, - "node" => node, - "node_id" => node_id, - "verse_id" => verse_id, - "selection" => selection}} = _payload, - %{assigns: %{kv_verses: verses}} = socket) do - # bind = %{node => %{node_id => %{field => %{"selection" => selection}}}} + %{"binding" => bind = %{"verse_id" => verse_id}}, + %{assigns: %{kv_verses: verses, marks: [%Mark{state: :draft, verse_id: curr_verse_id} = d_mark | marks]}} = socket) when is_binary(curr_verse_id) and verse_id != curr_verse_id do + + # binding here blocks the stream from appending to quote + bind = Draft.bind_node(bind) + + {:noreply, socket + |> stream_insert(:verses, + %{verses[curr_verse_id] | binding: nil}) + |> stream_insert(:verses, + %{verses[verse_id] | binding: bind}) + |> assign(:marks, [%{d_mark | binding: bind, verse_id: verse_id} | marks]) + } + end + + # already in mark in drafting state, remember to late bind binding => with a fn() + def handle_event("bindHoveRune", + %{"binding" => bind = %{"verse_id" => verse_id}}, + %{assigns: %{kv_verses: verses, marks: [%Mark{state: :draft} = d_mark | marks]}} = socket) do + + # binding here blocks the stream from appending to quote + bind = Draft.bind_node(bind) {:noreply, socket |> stream_insert(:verses, - %{verses[verse_id] | binding: %{node: node, - node_id: node_id, - field: field, - selection: selection}}) - |> push_event("genHoveRune", %{}) + %{verses[verse_id] | binding: bind}) + |> assign(:marks, [%{d_mark | binding: bind, verse_id: verse_id} | marks]) } end + @impl true + def handle_event("bindHoveRune", + %{"binding" => bind = %{"verse_id" => verse_id}}, + %{assigns: %{kv_verses: verses, marks: [%Mark{order: no} | _] = marks}} = socket) do + + bind = Draft.bind_node(bind) + + {:noreply, socket + |> stream_insert(:verses, + %{verses[verse_id] | binding: bind}) + |> assign(:marks, [%Mark{state: :draft, + order: no + 1, + verse_id: verse_id, + binding: bind} | marks])} + end + + @impl true + def handle_event("markQuote", _, %{assigns: %{marks: [%Mark{state: :draft} = d_mark | marks]}} = socket) do + IO.inspect(marks) + {:noreply, socket |> assign(:marks, [%{d_mark | state: :live} | marks])} + end + + def handle_event("markQuote", _, socket) do + {:noreply, socket} + end + + def handle_event("createMark", %{"body" => body}, %{assigns: %{marks: [%Mark{state: :draft} = d_mark | marks]}} = socket) do + {:noreply, socket |> assign(:marks, [%{d_mark | body: body, state: :live} | marks])} + end + + def handle_event("createMark", _event, socket) do + {:noreply, socket} + end + + + def handle_event(event, message , socket) do + IO.inspect(%{event: event, message: message}, label: "pokemon") + {:noreply, socket} + end + @doc """ Upon rcv of :media_handshake, which indicates an intention to sync by the player, @@ -119,7 +169,8 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do {:noreply, socket} end - def handle_info(msg, socket) do IO.inspect(msg, label: "unexpected message in @chapter") + def handle_info(msg, socket) do + IO.inspect(msg, label: "unexpected message in @chapter") {:noreply, socket} end @@ -162,6 +213,7 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do """ attr :id, :string, required: false attr :verse, :any, required: true + attr :marks, :any slot :edge, required: true do attr :title, :string attr(:node, :any, @@ -175,9 +227,10 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do attr :navigate, :any, required: false end + # enum.split() from @verse binding to mark def verse_matrix(assigns) do assigns = assigns - |> assign(:marginote_id, "marginote-#{Map.get(assigns, :id)}-#{Ecto.UUID.generate()}") + ~H"""

@@ -199,7 +252,16 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do
Enum.join("::")} class={"text-zinc-700 #{verse_class(elem.verseup)}"}> <%= Struct.get_in(Map.get(elem, :node, @verse), elem.field)%>
- <.comment_binding :if={@verse.binding} comments={@verse.comments} quote={@verse.binding.selection} class={(@verse.binding.node_id == Map.get(elem, :node, @verse).id && @verse.binding.field == elem.field |> Enum.join("::")) && "" || "hidden"} /> +
+ <.comment_binding comments={@verse.comments}/> + + ☙ ——— ›– ❊ –‹ ——— ❧ + <.drafting marks={@marks} quote={@verse.binding.window && @verse.binding.window.quote}/> +
@@ -215,24 +277,45 @@ defmodule VyasaWeb.SourceLive.Chapter.Index do "font-dn text-m" attr :class, :string, default: nil - attr :quote, :string, default: nil attr :comments, :any, default: nil def comment_binding(assigns) do assigns = assigns |> assign(:elem_id, "comment-modal-#{Ecto.UUID.generate()}") ~H""" -
<%= comment.body %> - <%= comment.signature %> + """ + end + + attr :quote, :string, default: nil + attr :marks, :list, default: [] + def drafting(assigns) do + assigns = assigns |> assign(:elem_id, "comment-modal-#{Ecto.UUID.generate()}") + + ~H""" +
+ + <%= mark.binding.window.quote %> + + + <%= mark.body %> - <%= "Self" %> + + +
+ <%= @quote %> - - <.form for={%{}} phx-submit="create_comment"> - + + +
+ <.form for={%{}} phx-submit="createMark"> + + +
+
- """ - end - - - attr(:id, :string, required: true) - attr(:current_user, :map, required: true) - attr(:show, :boolean, - default: true, - doc: "Default value is not to show the message" - ) - attr(:path, :string, default: "/") - - def modal_comments(assigns) do - assigns = assigns - - ~H""" - <.modal_wrapper id={@id} background="bg-black/50" close_button={true} main_width="lg:max-w-lg"> -
-
- -
- -
- - -
+
- +
""" end - end diff --git a/lib/vyasa_web/live/source_live/chapter/index.html.heex b/lib/vyasa_web/live/source_live/chapter/index.html.heex index ae94016c..7b18e83d 100644 --- a/lib/vyasa_web/live/source_live/chapter/index.html.heex +++ b/lib/vyasa_web/live/source_live/chapter/index.html.heex @@ -14,7 +14,7 @@
- <.verse_matrix id={dom_id} verse={verse} :for={{dom_id, %Written.Verse{} = verse} <- @streams.verses}> + <.verse_matrix id={dom_id} verse={verse} :for={{dom_id, %Written.Verse{} = verse} <- @streams.verses} marks={@marks}> <:edge title={"#{verse.chapter_no}.#{verse.no}"} field={[:body]} @@ -39,10 +39,10 @@