From 1ec5459a79919c109e02784494a178be529bf0a7 Mon Sep 17 00:00:00 2001 From: Silas Date: Fri, 24 Nov 2023 20:24:40 -0500 Subject: [PATCH] persist document to database after 2 seconds of no updates, initialize with current state when joining genserver --- assets/js/hooks/textEditHook.js | 5 ++++ lib/poex/pads/document.ex | 4 +-- lib/poex/pads/document_server.ex | 30 ++++++++++++++++--- lib/poex_web/live/pad/pad_live.ex | 13 ++++---- ...ove_state_and_add_contents_to_document.exs | 10 +++++++ 5 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 priv/repo/migrations/20231125011258_remove_state_and_add_contents_to_document.exs diff --git a/assets/js/hooks/textEditHook.js b/assets/js/hooks/textEditHook.js index 305ca4f..5819e96 100644 --- a/assets/js/hooks/textEditHook.js +++ b/assets/js/hooks/textEditHook.js @@ -28,6 +28,11 @@ export let TextEditor = { range && this.quill.setSelection(range.index, range.length); }) + channel.on("saved", () => { + console.log('Saved'); + // TODO: Show a saved message + }) + this.quill.on('text-change', (delta, oldDelta, source) => { if (delta == oldDelta) return; if (source == 'api') { diff --git a/lib/poex/pads/document.ex b/lib/poex/pads/document.ex index a3752dd..3ce5a13 100644 --- a/lib/poex/pads/document.ex +++ b/lib/poex/pads/document.ex @@ -9,7 +9,7 @@ defmodule Poex.Pads.Document do @primary_key {:id, Ecto.UUID, autogenerate: true} schema "pad_documents" do field :title, :string - field :state, :map, default: %{ops: []} + field :contents, {:array, :map}, default: [] has_many :operations, Poex.Pads.Operation timestamps(type: :utc_datetime) @@ -18,7 +18,7 @@ defmodule Poex.Pads.Document do @doc false def changeset(document, attrs) do document - |> cast(attrs, [:title, :state]) + |> cast(attrs, [:title, :contents]) |> cast_assoc(:operations) end end diff --git a/lib/poex/pads/document_server.ex b/lib/poex/pads/document_server.ex index a786aa7..9db4a74 100644 --- a/lib/poex/pads/document_server.ex +++ b/lib/poex/pads/document_server.ex @@ -1,8 +1,11 @@ defmodule Poex.Pads.DocumentServer do use GenServer + alias Poex.Pads.Document + alias Poex.Pads alias Poex.Utils.DeltaUtils @initial_state %{ + save_scheduled: false, # Number of changes made to the document so far version: 0, @@ -36,7 +39,8 @@ defmodule Poex.Pads.DocumentServer do def init(args) do id = args.id Registry.register(Poex.Pads.DocumentRegistry, id, []) - initial_state = Map.put(@initial_state, :id, id) + %Document{contents: current_state} = Pads.get_pad_document(id) + initial_state = Map.put(@initial_state, :id, id) |> Map.put(:contents, current_state) {:ok, initial_state} end @@ -50,19 +54,28 @@ defmodule Poex.Pads.DocumentServer do change = DeltaUtils.convert_ops(change_map) inverted = Delta.invert(change, state.contents) - state = %{ + save_scheduled = + if state.save_scheduled do + true + else + Process.send_after(self(), :save, 2000) + true + end + + new_state = %{ id: state.id, + save_scheduled: save_scheduled, version: state.version + 1, contents: Delta.compose(state.contents, change), inverted_changes: [inverted | state.inverted_changes] } - PoexWeb.Endpoint.broadcast("pad:#{state.id}", "update", %{ + PoexWeb.Endpoint.broadcast("pad:#{new_state.id}", "update", %{ change: change, client_id: client_id }) - {:reply, state.contents, state} + {:reply, new_state.contents, new_state} end # Fetch the current contents of the document @@ -100,6 +113,7 @@ defmodule Poex.Pads.DocumentServer do state = %{ id: state.id, + save_scheduled: state.save_scheduled, version: state.version - 1, contents: Delta.compose(state.contents, last_change), inverted_changes: changes @@ -108,5 +122,13 @@ defmodule Poex.Pads.DocumentServer do {:reply, state.contents, state} end + @impl true + def handle_info(:save, state) do + state = Map.put(state, :save_scheduled, false) + Pads.update_pad_document(state.id, %{contents: state.contents}) + PoexWeb.Endpoint.broadcast("pad:#{state.id}", "saved", %{}) + {:noreply, state} + end + defp via_tuple(id), do: {:via, Registry, {Poex.Pads.DocumentRegistry, id}} end diff --git a/lib/poex_web/live/pad/pad_live.ex b/lib/poex_web/live/pad/pad_live.ex index 851a31f..0c70de4 100644 --- a/lib/poex_web/live/pad/pad_live.ex +++ b/lib/poex_web/live/pad/pad_live.ex @@ -1,6 +1,5 @@ defmodule PoexWeb.PadLive do - alias Poex.Pads - alias Poex.Pads.Document + alias Poex.Pads.{Document, DocumentDynamicSupervisor} alias Poex.Repo alias Poex.Utils @@ -12,7 +11,7 @@ defmodule PoexWeb.PadLive do Document.changeset(%Document{}, %{title: "Untitled"}) |> Repo.insert() - Poex.Pads.DocumentDynamicSupervisor.start_document_supervisor(new_document) + DocumentDynamicSupervisor.start_document_supervisor(new_document) # Redirect to the new document with its ID {:ok, push_navigate(socket, to: ~p"/pad/#{new_document.id}", replace: true)} @@ -22,11 +21,13 @@ defmodule PoexWeb.PadLive do %{ id: id, title: title, - state: state - } = Repo.get!(Document, id) + contents: contents + } = document = Repo.get!(Document, id) + + DocumentDynamicSupervisor.start_document_supervisor(document) # init editor and assigns with latest state from doc - {:ok, assign(socket, id: id, title: title, state: state |> Utils.atomize_keys())} + {:ok, assign(socket, id: id, title: title, contents: contents |> Utils.atomize_keys())} end def handle_info(:new_document, socket) do diff --git a/priv/repo/migrations/20231125011258_remove_state_and_add_contents_to_document.exs b/priv/repo/migrations/20231125011258_remove_state_and_add_contents_to_document.exs new file mode 100644 index 0000000..e293697 --- /dev/null +++ b/priv/repo/migrations/20231125011258_remove_state_and_add_contents_to_document.exs @@ -0,0 +1,10 @@ +defmodule Poex.Repo.Migrations.RemoveStateAndAddContentsToDocument do + use Ecto.Migration + + def change do + alter table(:pad_documents) do + remove :state + add :contents, {:array, :map}, default: [] + end + end +end