add steps
This commit is contained in:
		
							
								
								
									
										44
									
								
								lib/memex_web/components/step_content.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								lib/memex_web/components/step_content.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| defmodule MemexWeb.Components.StepContent do | ||||
|   @moduledoc """ | ||||
|   Display the content for a step | ||||
|   """ | ||||
|   use MemexWeb, :component | ||||
|   alias Memex.Pipelines.Steps.Step | ||||
|   alias Phoenix.HTML | ||||
|  | ||||
|   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" | ||||
|       phx-hook="MaintainAttrs" | ||||
|       phx-update="ignore" | ||||
|       readonly | ||||
|       phx-no-format | ||||
|     ><p class="inline"><%= add_links_to_content(@step.content) %></p></div> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   defp add_links_to_content(content) do | ||||
|     Regex.replace( | ||||
|       ~r/\[\[([\p{L}\p{N}\-]+)\]\]/, | ||||
|       content, | ||||
|       fn _whole_match, slug -> | ||||
|         link = | ||||
|           HTML.Link.link( | ||||
|             "[[#{slug}]]", | ||||
|             to: Routes.context_show_path(Endpoint, :show, slug), | ||||
|             class: "link inline", | ||||
|             data: [qa: "step-context-#{slug}"] | ||||
|           ) | ||||
|           |> HTML.Safe.to_iodata() | ||||
|           |> IO.iodata_to_binary() | ||||
|  | ||||
|         "</p>#{link}<p class=\"inline\">" | ||||
|       end | ||||
|     ) | ||||
|     |> HTML.raw() | ||||
|   end | ||||
| end | ||||
| @@ -1,7 +1,8 @@ | ||||
| defmodule MemexWeb.PipelineLive.Show do | ||||
|   use MemexWeb, :live_view | ||||
|  | ||||
|   alias Memex.{Accounts.User, Pipelines, Pipelines.Pipeline} | ||||
|   import MemexWeb.Components.StepContent | ||||
|   alias Memex.{Accounts.User, Pipelines} | ||||
|   alias Memex.Pipelines.{Pipeline, Steps, Steps.Step} | ||||
|  | ||||
|   @impl true | ||||
|   def mount(_params, _session, socket) do | ||||
| @@ -10,9 +11,9 @@ defmodule MemexWeb.PipelineLive.Show do | ||||
|  | ||||
|   @impl true | ||||
|   def handle_params( | ||||
|         %{"slug" => slug}, | ||||
|         _, | ||||
|         %{assigns: %{live_action: live_action, current_user: current_user}} = socket | ||||
|         %{"slug" => slug} = params, | ||||
|         _url, | ||||
|         %{assigns: %{current_user: current_user, live_action: live_action}} = socket | ||||
|       ) do | ||||
|     pipeline = | ||||
|       case Pipelines.get_pipeline_by_slug(slug, current_user) do | ||||
| @@ -24,10 +25,45 @@ defmodule MemexWeb.PipelineLive.Show do | ||||
|       socket | ||||
|       |> assign(:page_title, page_title(live_action, pipeline)) | ||||
|       |> assign(:pipeline, pipeline) | ||||
|       |> assign(:steps, pipeline |> Steps.list_steps(current_user)) | ||||
|       |> apply_action(live_action, params) | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   defp apply_action(socket, live_action, _params) when live_action in [:show, :edit] do | ||||
|     socket | ||||
|   end | ||||
|  | ||||
|   defp apply_action( | ||||
|          %{ | ||||
|            assigns: %{ | ||||
|              steps: steps, | ||||
|              pipeline: %{id: pipeline_id}, | ||||
|              current_user: %{id: current_user_id} | ||||
|            } | ||||
|          } = socket, | ||||
|          :add_step, | ||||
|          _params | ||||
|        ) do | ||||
|     socket | ||||
|     |> assign( | ||||
|       step: %Step{ | ||||
|         position: steps |> Enum.count(), | ||||
|         pipeline_id: pipeline_id, | ||||
|         user_id: current_user_id | ||||
|       } | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   defp apply_action( | ||||
|          %{assigns: %{current_user: current_user}} = socket, | ||||
|          :edit_step, | ||||
|          %{"step_id" => step_id} | ||||
|        ) do | ||||
|     socket |> assign(step: step_id |> Steps.get_step!(current_user)) | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event( | ||||
|         "delete", | ||||
| @@ -44,8 +80,51 @@ defmodule MemexWeb.PipelineLive.Show do | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event( | ||||
|         "delete_step", | ||||
|         %{"step-id" => step_id}, | ||||
|         %{assigns: %{pipeline: %{slug: pipeline_slug}, current_user: current_user}} = socket | ||||
|       ) do | ||||
|     {:ok, %{title: title}} = | ||||
|       step_id | ||||
|       |> Steps.get_step!(current_user) | ||||
|       |> Steps.delete_step(current_user) | ||||
|  | ||||
|     socket = | ||||
|       socket | ||||
|       |> put_flash(:info, gettext("%{title} deleted", title: title)) | ||||
|       |> push_patch(to: Routes.pipeline_show_path(Endpoint, :show, pipeline_slug)) | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event( | ||||
|         "reorder_step", | ||||
|         %{"step-id" => step_id, "direction" => direction}, | ||||
|         %{assigns: %{pipeline: %{slug: pipeline_slug}, current_user: current_user}} = socket | ||||
|       ) do | ||||
|     direction = if direction == "up", do: :up, else: :down | ||||
|  | ||||
|     {:ok, _step} = | ||||
|       step_id | ||||
|       |> Steps.get_step!(current_user) | ||||
|       |> Steps.reorder_step(direction, current_user) | ||||
|  | ||||
|     socket = | ||||
|       socket | ||||
|       |> push_patch(to: Routes.pipeline_show_path(Endpoint, :show, pipeline_slug)) | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   defp page_title(:show, %{slug: slug}), do: slug | ||||
|   defp page_title(:edit, %{slug: slug}), do: gettext("edit %{slug}", slug: slug) | ||||
|  | ||||
|   defp page_title(live_action, %{slug: slug}) when live_action in [:edit, :edit_step], | ||||
|     do: gettext("edit %{slug}", slug: slug) | ||||
|  | ||||
|   defp page_title(:add_step, %{slug: slug}), do: gettext("add step to %{slug}", slug: slug) | ||||
|  | ||||
|   @spec is_owner_or_admin?(Pipeline.t(), User.t()) :: boolean() | ||||
|   defp is_owner_or_admin?(%{user_id: user_id}, %{id: user_id}), do: true | ||||
|   | ||||
| @@ -5,20 +5,22 @@ | ||||
|  | ||||
|   <p><%= if @pipeline.tags, do: @pipeline.tags |> Enum.join(", ") %></p> | ||||
|  | ||||
|   <textarea | ||||
|     id="show-pipeline-description" | ||||
|     class="input input-primary h-128 min-h-128" | ||||
|     phx-hook="MaintainAttrs" | ||||
|     phx-update="ignore" | ||||
|     readonly | ||||
|     phx-no-format | ||||
|   ><%= @pipeline.description %></textarea> | ||||
|   <%= if @pipeline.description do %> | ||||
|     <textarea | ||||
|       id="show-pipeline-description" | ||||
|       class="input input-primary h-32 min-h-32" | ||||
|       phx-hook="MaintainAttrs" | ||||
|       phx-update="ignore" | ||||
|       readonly | ||||
|       phx-no-format | ||||
|     ><%= @pipeline.description %></textarea> | ||||
|   <% end %> | ||||
|  | ||||
|   <p class="self-end"> | ||||
|     <%= gettext("Visibility: %{visibility}", visibility: @pipeline.visibility) %> | ||||
|   </p> | ||||
|  | ||||
|   <div class="self-end flex space-x-4"> | ||||
|   <div class="pb-4 self-end flex space-x-4"> | ||||
|     <.link class="btn btn-primary" navigate={Routes.pipeline_index_path(@socket, :index)}> | ||||
|       <%= dgettext("actions", "back") %> | ||||
|     </.link> | ||||
| @@ -42,18 +44,131 @@ | ||||
|       </button> | ||||
|     <% end %> | ||||
|   </div> | ||||
|  | ||||
|   <hr class="hr" /> | ||||
|  | ||||
|   <h2 class="pt-2 self-center text-lg"> | ||||
|     <%= gettext("steps:") %> | ||||
|   </h2> | ||||
|  | ||||
|   <%= if @steps |> Enum.empty?() do %> | ||||
|     <h3 class="self-center text-md text-primary-600"> | ||||
|       <%= gettext("no steps") %> | ||||
|     </h3> | ||||
|   <% else %> | ||||
|     <%= for %{id: step_id, position: position, title: title} = step <- @steps do %> | ||||
|       <div class="flex justify-between items-center space-x-4"> | ||||
|         <h3 class="text-md"> | ||||
|           <%= gettext("%{position}. %{title}", position: position + 1, title: title) %> | ||||
|         </h3> | ||||
|  | ||||
|         <%= if is_owner?(@pipeline, @current_user) do %> | ||||
|           <div class="flex justify-between items-center space-x-4"> | ||||
|             <%= if position <= 0 do %> | ||||
|               <i class="fas text-xl fa-chevron-up cursor-not-allowed opacity-25"></i> | ||||
|             <% else %> | ||||
|               <button | ||||
|                 type="button" | ||||
|                 class="cursor-pointer flex justify-center items-center" | ||||
|                 phx-click="reorder_step" | ||||
|                 phx-value-direction="up" | ||||
|                 phx-value-step-id={step_id} | ||||
|                 data-qa={"move-step-up-#{step_id}"} | ||||
|               > | ||||
|                 <i class="fas text-xl fa-chevron-up"></i> | ||||
|               </button> | ||||
|             <% end %> | ||||
|  | ||||
|             <%= if position >= length(@steps) - 1 do %> | ||||
|               <i class="fas text-xl fa-chevron-down cursor-not-allowed opacity-25"></i> | ||||
|             <% else %> | ||||
|               <button | ||||
|                 type="button" | ||||
|                 class="cursor-pointer flex justify-center items-center" | ||||
|                 phx-click="reorder_step" | ||||
|                 phx-value-direction="down" | ||||
|                 phx-value-step-id={step_id} | ||||
|                 data-qa={"move-step-down-#{step_id}"} | ||||
|               > | ||||
|                 <i class="fas text-xl fa-chevron-down"></i> | ||||
|               </button> | ||||
|             <% end %> | ||||
|  | ||||
|             <.link | ||||
|               class="self-end btn btn-primary" | ||||
|               patch={Routes.pipeline_show_path(@socket, :edit_step, @pipeline.slug, step_id)} | ||||
|               data-qa={"edit-step-#{step_id}"} | ||||
|             > | ||||
|               <%= dgettext("actions", "edit") %> | ||||
|             </.link> | ||||
|  | ||||
|             <button | ||||
|               type="button" | ||||
|               class="btn btn-primary" | ||||
|               phx-click="delete_step" | ||||
|               phx-value-step-id={step_id} | ||||
|               data-confirm={dgettext("prompts", "are you sure?")} | ||||
|               data-qa={"delete-step-#{step_id}"} | ||||
|             > | ||||
|               <%= dgettext("actions", "delete") %> | ||||
|             </button> | ||||
|           </div> | ||||
|         <% end %> | ||||
|       </div> | ||||
|  | ||||
|       <.step_content step={step} /> | ||||
|     <% end %> | ||||
|   <% end %> | ||||
|  | ||||
|   <%= if is_owner?(@pipeline, @current_user) do %> | ||||
|     <.link | ||||
|       class="self-end btn btn-primary" | ||||
|       patch={Routes.pipeline_show_path(@socket, :add_step, @pipeline.slug)} | ||||
|       data-qa={"add-step-#{@pipeline.id}"} | ||||
|     > | ||||
|       <%= dgettext("actions", "add step") %> | ||||
|     </.link> | ||||
|   <% end %> | ||||
| </div> | ||||
|  | ||||
| <%= if @live_action in [:edit] do %> | ||||
|   <.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}> | ||||
|     <.live_component | ||||
|       module={MemexWeb.PipelineLive.FormComponent} | ||||
|       id={@pipeline.id} | ||||
|       current_user={@current_user} | ||||
|       title={@page_title} | ||||
|       action={@live_action} | ||||
|       pipeline={@pipeline} | ||||
|       return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)} | ||||
|     /> | ||||
|   </.modal> | ||||
| <%= case @live_action do %> | ||||
|   <% :edit -> %> | ||||
|     <.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}> | ||||
|       <.live_component | ||||
|         module={MemexWeb.PipelineLive.FormComponent} | ||||
|         id={@pipeline.id} | ||||
|         current_user={@current_user} | ||||
|         title={@page_title} | ||||
|         action={@live_action} | ||||
|         pipeline={@pipeline} | ||||
|         return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)} | ||||
|       /> | ||||
|     </.modal> | ||||
|   <% :add_step -> %> | ||||
|     <.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}> | ||||
|       <.live_component | ||||
|         module={MemexWeb.StepLive.FormComponent} | ||||
|         id={@pipeline.id || :new} | ||||
|         current_user={@current_user} | ||||
|         title={@page_title} | ||||
|         action={@live_action} | ||||
|         pipeline={@pipeline} | ||||
|         step={@step} | ||||
|         return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)} | ||||
|       /> | ||||
|     </.modal> | ||||
|   <% :edit_step -> %> | ||||
|     <.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}> | ||||
|       <.live_component | ||||
|         module={MemexWeb.StepLive.FormComponent} | ||||
|         id={@pipeline.id || :new} | ||||
|         current_user={@current_user} | ||||
|         title={@page_title} | ||||
|         action={@live_action} | ||||
|         pipeline={@pipeline} | ||||
|         step={@step} | ||||
|         return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)} | ||||
|       /> | ||||
|     </.modal> | ||||
|   <% _ -> %> | ||||
| <% end %> | ||||
|   | ||||
							
								
								
									
										74
									
								
								lib/memex_web/live/step_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								lib/memex_web/live/step_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| defmodule MemexWeb.StepLive.FormComponent do | ||||
|   use MemexWeb, :live_component | ||||
|  | ||||
|   alias Memex.Pipelines.Steps | ||||
|  | ||||
|   @impl true | ||||
|   def update(%{step: step, current_user: current_user, pipeline: _pipeline} = assigns, socket) do | ||||
|     changeset = Steps.change_step(step, current_user) | ||||
|  | ||||
|     {:ok, | ||||
|      socket | ||||
|      |> assign(assigns) | ||||
|      |> assign(:changeset, changeset)} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event( | ||||
|         "validate", | ||||
|         %{"step" => step_params}, | ||||
|         %{assigns: %{step: step, current_user: current_user}} = socket | ||||
|       ) do | ||||
|     changeset = | ||||
|       step | ||||
|       |> Steps.change_step(step_params, current_user) | ||||
|       |> Map.put(:action, :validate) | ||||
|  | ||||
|     {:noreply, assign(socket, :changeset, changeset)} | ||||
|   end | ||||
|  | ||||
|   def handle_event("save", %{"step" => step_params}, %{assigns: %{action: action}} = socket) do | ||||
|     save_step(socket, action, step_params) | ||||
|   end | ||||
|  | ||||
|   defp save_step( | ||||
|          %{assigns: %{step: step, return_to: return_to, current_user: current_user}} = socket, | ||||
|          :edit_step, | ||||
|          step_params | ||||
|        ) do | ||||
|     case Steps.update_step(step, step_params, current_user) do | ||||
|       {:ok, %{title: title}} -> | ||||
|         {:noreply, | ||||
|          socket | ||||
|          |> put_flash(:info, gettext("%{title} saved", title: title)) | ||||
|          |> push_navigate(to: return_to)} | ||||
|  | ||||
|       {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         {:noreply, assign(socket, :changeset, changeset)} | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   defp save_step( | ||||
|          %{ | ||||
|            assigns: %{ | ||||
|              step: %{position: position}, | ||||
|              return_to: return_to, | ||||
|              current_user: current_user, | ||||
|              pipeline: pipeline | ||||
|            } | ||||
|          } = socket, | ||||
|          :add_step, | ||||
|          step_params | ||||
|        ) do | ||||
|     case Steps.create_step(step_params, position, pipeline, current_user) do | ||||
|       {:ok, %{title: title}} -> | ||||
|         {:noreply, | ||||
|          socket | ||||
|          |> put_flash(:info, gettext("%{title} created", title: title)) | ||||
|          |> push_navigate(to: return_to)} | ||||
|  | ||||
|       {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         {:noreply, assign(socket, changeset: changeset)} | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										34
									
								
								lib/memex_web/live/step_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								lib/memex_web/live/step_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <div class="h-full flex flex-col justify-start items-stretch space-y-4"> | ||||
|   <.form | ||||
|     :let={f} | ||||
|     for={@changeset} | ||||
|     id="step-form" | ||||
|     phx-target={@myself} | ||||
|     phx-change="validate" | ||||
|     phx-submit="save" | ||||
|     phx-debounce="300" | ||||
|     class="flex flex-col justify-start items-stretch space-y-4" | ||||
|   > | ||||
|     <%= text_input(f, :title, | ||||
|       class: "input input-primary", | ||||
|       placeholder: gettext("title") | ||||
|     ) %> | ||||
|     <%= error_tag(f, :title) %> | ||||
|  | ||||
|     <%= textarea(f, :content, | ||||
|       id: "step-form-content", | ||||
|       class: "input input-primary h-64 min-h-64", | ||||
|       phx_hook: "MaintainAttrs", | ||||
|       phx_update: "ignore", | ||||
|       placeholder: gettext("use [[context-slug]] to link to a context") | ||||
|     ) %> | ||||
|     <%= error_tag(f, :content) %> | ||||
|  | ||||
|     <div class="flex justify-center items-stretch space-x-4"> | ||||
|       <%= submit(dgettext("actions", "save"), | ||||
|         phx_disable_with: gettext("saving..."), | ||||
|         class: "mx-auto btn btn-primary" | ||||
|       ) %> | ||||
|     </div> | ||||
|   </.form> | ||||
| </div> | ||||
| @@ -69,6 +69,8 @@ defmodule MemexWeb.Router do | ||||
|       live "/pipelines/new", PipelineLive.Index, :new | ||||
|       live "/pipelines/:slug/edit", PipelineLive.Index, :edit | ||||
|       live "/pipeline/:slug/edit", PipelineLive.Show, :edit | ||||
|       live "/pipeline/:slug/add_step", PipelineLive.Show, :add_step | ||||
|       live "/pipeline/:slug/:step_id", PipelineLive.Show, :edit_step | ||||
|  | ||||
|       get "/users/settings", UserSettingsController, :edit | ||||
|       put "/users/settings", UserSettingsController, :update | ||||
|   | ||||
		Reference in New Issue
	
	Block a user