persist document to database after 2 seconds of no updates, initialize with current state when joining genserver
This commit is contained in:
parent
52a7a64d23
commit
1ec5459a79
|
@ -28,6 +28,11 @@ export let TextEditor = {
|
||||||
range && this.quill.setSelection(range.index, range.length);
|
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) => {
|
this.quill.on('text-change', (delta, oldDelta, source) => {
|
||||||
if (delta == oldDelta) return;
|
if (delta == oldDelta) return;
|
||||||
if (source == 'api') {
|
if (source == 'api') {
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Poex.Pads.Document do
|
||||||
@primary_key {:id, Ecto.UUID, autogenerate: true}
|
@primary_key {:id, Ecto.UUID, autogenerate: true}
|
||||||
schema "pad_documents" do
|
schema "pad_documents" do
|
||||||
field :title, :string
|
field :title, :string
|
||||||
field :state, :map, default: %{ops: []}
|
field :contents, {:array, :map}, default: []
|
||||||
has_many :operations, Poex.Pads.Operation
|
has_many :operations, Poex.Pads.Operation
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
|
@ -18,7 +18,7 @@ defmodule Poex.Pads.Document do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(document, attrs) do
|
def changeset(document, attrs) do
|
||||||
document
|
document
|
||||||
|> cast(attrs, [:title, :state])
|
|> cast(attrs, [:title, :contents])
|
||||||
|> cast_assoc(:operations)
|
|> cast_assoc(:operations)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
defmodule Poex.Pads.DocumentServer do
|
defmodule Poex.Pads.DocumentServer do
|
||||||
use GenServer
|
use GenServer
|
||||||
|
alias Poex.Pads.Document
|
||||||
|
alias Poex.Pads
|
||||||
alias Poex.Utils.DeltaUtils
|
alias Poex.Utils.DeltaUtils
|
||||||
|
|
||||||
@initial_state %{
|
@initial_state %{
|
||||||
|
save_scheduled: false,
|
||||||
# Number of changes made to the document so far
|
# Number of changes made to the document so far
|
||||||
version: 0,
|
version: 0,
|
||||||
|
|
||||||
|
@ -36,7 +39,8 @@ defmodule Poex.Pads.DocumentServer do
|
||||||
def init(args) do
|
def init(args) do
|
||||||
id = args.id
|
id = args.id
|
||||||
Registry.register(Poex.Pads.DocumentRegistry, 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}
|
{:ok, initial_state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,19 +54,28 @@ defmodule Poex.Pads.DocumentServer do
|
||||||
change = DeltaUtils.convert_ops(change_map)
|
change = DeltaUtils.convert_ops(change_map)
|
||||||
inverted = Delta.invert(change, state.contents)
|
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,
|
id: state.id,
|
||||||
|
save_scheduled: save_scheduled,
|
||||||
version: state.version + 1,
|
version: state.version + 1,
|
||||||
contents: Delta.compose(state.contents, change),
|
contents: Delta.compose(state.contents, change),
|
||||||
inverted_changes: [inverted | state.inverted_changes]
|
inverted_changes: [inverted | state.inverted_changes]
|
||||||
}
|
}
|
||||||
|
|
||||||
PoexWeb.Endpoint.broadcast("pad:#{state.id}", "update", %{
|
PoexWeb.Endpoint.broadcast("pad:#{new_state.id}", "update", %{
|
||||||
change: change,
|
change: change,
|
||||||
client_id: client_id
|
client_id: client_id
|
||||||
})
|
})
|
||||||
|
|
||||||
{:reply, state.contents, state}
|
{:reply, new_state.contents, new_state}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Fetch the current contents of the document
|
# Fetch the current contents of the document
|
||||||
|
@ -100,6 +113,7 @@ defmodule Poex.Pads.DocumentServer do
|
||||||
|
|
||||||
state = %{
|
state = %{
|
||||||
id: state.id,
|
id: state.id,
|
||||||
|
save_scheduled: state.save_scheduled,
|
||||||
version: state.version - 1,
|
version: state.version - 1,
|
||||||
contents: Delta.compose(state.contents, last_change),
|
contents: Delta.compose(state.contents, last_change),
|
||||||
inverted_changes: changes
|
inverted_changes: changes
|
||||||
|
@ -108,5 +122,13 @@ defmodule Poex.Pads.DocumentServer do
|
||||||
{:reply, state.contents, state}
|
{:reply, state.contents, state}
|
||||||
end
|
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}}
|
defp via_tuple(id), do: {:via, Registry, {Poex.Pads.DocumentRegistry, id}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
defmodule PoexWeb.PadLive do
|
defmodule PoexWeb.PadLive do
|
||||||
alias Poex.Pads
|
alias Poex.Pads.{Document, DocumentDynamicSupervisor}
|
||||||
alias Poex.Pads.Document
|
|
||||||
alias Poex.Repo
|
alias Poex.Repo
|
||||||
alias Poex.Utils
|
alias Poex.Utils
|
||||||
|
|
||||||
|
@ -12,7 +11,7 @@ defmodule PoexWeb.PadLive do
|
||||||
Document.changeset(%Document{}, %{title: "Untitled"})
|
Document.changeset(%Document{}, %{title: "Untitled"})
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
|
|
||||||
Poex.Pads.DocumentDynamicSupervisor.start_document_supervisor(new_document)
|
DocumentDynamicSupervisor.start_document_supervisor(new_document)
|
||||||
|
|
||||||
# Redirect to the new document with its ID
|
# Redirect to the new document with its ID
|
||||||
{:ok, push_navigate(socket, to: ~p"/pad/#{new_document.id}", replace: true)}
|
{:ok, push_navigate(socket, to: ~p"/pad/#{new_document.id}", replace: true)}
|
||||||
|
@ -22,11 +21,13 @@ defmodule PoexWeb.PadLive do
|
||||||
%{
|
%{
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
state: state
|
contents: contents
|
||||||
} = Repo.get!(Document, id)
|
} = document = Repo.get!(Document, id)
|
||||||
|
|
||||||
|
DocumentDynamicSupervisor.start_document_supervisor(document)
|
||||||
|
|
||||||
# init editor and assigns with latest state from doc
|
# 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
|
end
|
||||||
|
|
||||||
def handle_info(:new_document, socket) do
|
def handle_info(:new_document, socket) do
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue