mirror of
https://github.com/plausible/analytics.git
synced 2024-12-24 01:54:34 +03:00
wip
This commit is contained in:
parent
e3b8067628
commit
265d21efc8
@ -22,6 +22,11 @@ liveSocket.connect()
|
||||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket
|
||||
|
||||
window.addEventListener(`phx:update-value`, (e) => {
|
||||
let el = document.getElementById(e.detail.id)
|
||||
el.value = e.detail.value
|
||||
})
|
||||
|
||||
const triggers = document.querySelectorAll('[data-dropdown-trigger]')
|
||||
|
||||
for (const trigger of triggers) {
|
||||
|
@ -22,12 +22,27 @@ defmodule PlausibleWeb.Live.FunnelSettings do
|
||||
<%= if @add_funnel? do %>
|
||||
<.live_component module={PlausibleWeb.Live.FunnelSettings.Form} id="funnelForm" site={@site} form={to_form(Plausible.Funnel.changeset())} goals={@goals} />
|
||||
<% else %>
|
||||
<.live_component module={PlausibleWeb.Live.FunnelSettings.List} id="funnelsList" funnels={@funnels} site={@site} />
|
||||
<button type="button" class="button mt-6" phx-click="add_funnel">+ Add funnel</button>
|
||||
<div :if={Enum.count(@goals) >= 2}>
|
||||
<.live_component module={PlausibleWeb.Live.FunnelSettings.List} id="funnelsList" funnels={@funnels} site={@site} />
|
||||
<button type="button" class="button mt-6" phx-click="add_funnel">+ Add funnel</button>
|
||||
</div>
|
||||
<div :if={Enum.count(@goals) < 2}>
|
||||
|
||||
<div class="rounded-md bg-yellow-100 p-4 mt-8">
|
||||
<p class="text-sm leading-5 text-gray-900 dark:text-gray-100">
|
||||
You need to define at least two goals to create a funnel. Go ahead and <%= link "add goals", to: PlausibleWeb.Router.Helpers.site_path(@socket, :new_goal, @site.domain), class: "text-indigo-500 w-full text-center" %> to proceed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_event("save", data, socket) do
|
||||
IO.inspect(data, label: :save)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("add_funnel", _value, socket) do
|
||||
{:noreply, assign(socket, :add_funnel?, true)}
|
||||
end
|
||||
@ -40,4 +55,9 @@ defmodule PlausibleWeb.Live.FunnelSettings do
|
||||
IO.inspect(value, label: :validate)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_info(:goal_picked, socket) do
|
||||
IO.inspect("Picker notified")
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
@ -16,23 +16,24 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
|
||||
~H"""
|
||||
<div class="grid grid-cols-4 gap-6 mt-6">
|
||||
<div class="col-span-4 sm:col-span-2">
|
||||
<.form :let={f} for={@form} phx-change="validate" phx-submit="save">
|
||||
<.form :let={f} for={@form} phx-change="validate" phx-submit="save" onkeydown="return event.key != 'Enter';">
|
||||
<%= label f, "Funnel name", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||
<.input field={@form[:name]} />
|
||||
|
||||
<%= label f, "Funnel Steps", class: "mt-6 block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||
|
||||
<%= for _step_number <- 1..@step_count do %>
|
||||
<.select goals={@goals} myself={@myself} />
|
||||
<% end %>
|
||||
<.live_component
|
||||
:for={step_number <- 1..@step_count}
|
||||
module={PlausibleWeb.Live.FunnelSettings.InputPicker}
|
||||
id={"step-#{step_number}"}
|
||||
options={Enum.map(@goals, fn goal -> {goal.id, Plausible.Goal.display_name(goal)} end)}
|
||||
/>
|
||||
|
||||
<%= if @step_count < 5 do %>
|
||||
<a class="underline text-indigo-600 cursor-pointer" phx-click="add-step" phx-target={@myself}>Add another step</a>
|
||||
<% end %>
|
||||
<a :if={@step_count < 5} class="underline text-indigo-600 cursor-pointer" phx-click="add-step" phx-target={@myself}>Add another step</a>
|
||||
|
||||
<br/><hr/>
|
||||
|
||||
<button type="button" class="button mt-6">Save</button>
|
||||
<button type="submit" class="button mt-6">Save</button>
|
||||
<button type="button" class="button mt-6" phx-click="cancel_add_funnel">Cancel</button>
|
||||
</.form>
|
||||
</div>
|
||||
@ -40,26 +41,11 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
|
||||
"""
|
||||
end
|
||||
|
||||
attr :field, Phoenix.HTML.FormField
|
||||
attr(:field, Phoenix.HTML.FormField)
|
||||
|
||||
def input(assigns) do
|
||||
~H"""
|
||||
<input type="text" id={@field.id} name={@field.name} value={@field.value} class="focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-900 dark:text-gray-300 block w-full rounded-md sm:text-sm border-gray-300 dark:border-gray-500" />
|
||||
"""
|
||||
end
|
||||
|
||||
attr :goals, :map, required: true
|
||||
attr :myself, :any, required: true
|
||||
|
||||
def select(assigns) do
|
||||
~H"""
|
||||
<select phx-change="changed" phx-target={@myself} name="steps[]" class="dark:bg-gray-900 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 cursor-pointer" id="site_timezone">
|
||||
<%= for goal <- @goals do %>
|
||||
<option value={goal.id}>
|
||||
<%= Plausible.Goal.display_name(goal) %>
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<input type="text" id={@field.id} name={@field.name} value={@field.value} phx-debounce="300" class="focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-900 dark:text-gray-300 block w-full rounded-md sm:text-sm border-gray-300 dark:border-gray-500" />
|
||||
"""
|
||||
end
|
||||
|
||||
@ -80,7 +66,3 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
|
||||
{:noreply, assign(socket, goals: except_first)}
|
||||
end
|
||||
end
|
||||
|
||||
# goal_options = [{"N/A", ""} | Enum.map(@goals, fn goal -> {Plausible.Goal.display_name(goal), goal.id} end)] %>
|
||||
#
|
||||
# end
|
||||
|
180
lib/plausible_web/live/funnel_settings/input_picker.ex
Normal file
180
lib/plausible_web/live/funnel_settings/input_picker.ex
Normal file
@ -0,0 +1,180 @@
|
||||
defmodule PlausibleWeb.Live.FunnelSettings.InputPicker do
|
||||
use Phoenix.LiveComponent
|
||||
|
||||
attr :placeholder, :string, default: "Select a Goal"
|
||||
attr :id, :any, default: nil
|
||||
attr :options, :list, default: []
|
||||
attr :choices, :list, default: nil
|
||||
attr :show_picker?, :boolean, default: false
|
||||
attr :value, :string, default: ""
|
||||
attr :goal_id, :string, default: ""
|
||||
attr :higlighted, :integer, default: nil
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="mb-3">
|
||||
<div class="relative w-full">
|
||||
<div class="pl-2 pr-8 py-1 w-full dark:bg-gray-900 dark:text-gray-300 rounded-md shadow-sm border border-gray-300 dark:border-gray-700 focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500 ">
|
||||
|
||||
<input autocomplete="off" phx-debounce="10" phx-keyup="keypress" phx-target={@myself} name={@id} id={@id} class="border-none py-1 px-1 p-0 w-full inline-block rounded-md focus:outline-none focus:ring-0 text-sm" style="background-color: inherit;" placeholder={@placeholder} type="text" value={@value}>
|
||||
|
||||
<div phx-click="show-picker" phx-target={@myself} class="cursor-pointer absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="h-4 w-4 text-gray-500"><path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd"></path></svg>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<.picker id={"picker-#{@id}"} :if={@show_picker?} options={@options} choices={@choices || @options} target={@myself} higlighted={@higlighted} />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :id, :any, default: nil
|
||||
attr :options, :list, default: []
|
||||
attr :choices, :list, default: []
|
||||
attr :higlighted, :any
|
||||
attr :target, :any
|
||||
|
||||
def picker(assigns) do
|
||||
~H"""
|
||||
<ul phx-click-away="hide-picker" phx-target={@target} class="z-50 absolute mt-1 max-h-60 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm dark:bg-gray-900">
|
||||
<%= if @choices != [] do %>
|
||||
<%= for {goal_id, goal_name} <- @choices do %>
|
||||
<.li goal_id={goal_id} goal_name={goal_name} target={@target} higlighted={@higlighted}/>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="relative cursor-default select-none py-2 px-4 text-gray-700 dark:text-gray-300">No matches found in the current goals. Try searching for something different.</div>
|
||||
<% end %>
|
||||
</ul>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :higlighted, :any, default: nil
|
||||
attr :goal_name, :string, required: true
|
||||
attr :goal_id, :integer, required: true
|
||||
attr :target, :any
|
||||
|
||||
def li(assigns) do
|
||||
~H"""
|
||||
<%= if @higlighted == @goal_id do %>
|
||||
<li phx-value-goal-name={@goal_name}, phx-click="selected" phx-target={@target} class="relative select-none py-2 px-3 cursor-pointer dark:text-gray-300 bg-indigo-500 text-white">
|
||||
<span class="block truncate"><%= @goal_name %></span>
|
||||
</li>
|
||||
<% else %>
|
||||
<li phx-value-goal-name={@goal_name}, phx-click="selected" phx-target={@target} class="relative select-none py-2 px-3 cursor-pointer dark:text-gray-300 text-gray-500">
|
||||
<span class="block truncate"><%= @goal_name %></span>
|
||||
</li>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_event("selected", %{"goal-name" => goal_name}, socket) do
|
||||
send(self(), :goal_picked)
|
||||
{:noreply, assign(socket, %{show_picker?: false, value: goal_name})}
|
||||
end
|
||||
|
||||
def handle_event("keypress", %{"key" => "ArrowDown"}, socket) do
|
||||
choices = socket.assigns.choices
|
||||
current = socket.assigns.higlighted
|
||||
|
||||
next =
|
||||
Enum.drop_while(choices, fn {id, _} -> id != current end)
|
||||
|> Enum.take(2)
|
||||
|> List.last()
|
||||
|
||||
higlighted =
|
||||
case next do
|
||||
{next_id, _} ->
|
||||
next_id
|
||||
|
||||
nil ->
|
||||
current
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, %{higlighted: higlighted, show_picker?: true})}
|
||||
end
|
||||
|
||||
def handle_event("keypress", %{"key" => "ArrowUp"}, socket) do
|
||||
choices = socket.assigns.choices
|
||||
current = socket.assigns.higlighted
|
||||
|
||||
next =
|
||||
choices
|
||||
|> Enum.reverse()
|
||||
|> Enum.drop_while(fn {id, _} -> id != current end)
|
||||
|> Enum.take(2)
|
||||
|> List.last()
|
||||
|
||||
higlighted =
|
||||
case next do
|
||||
{next_id, _} ->
|
||||
next_id
|
||||
|
||||
nil ->
|
||||
current
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, %{higlighted: higlighted, show_picker?: true})}
|
||||
end
|
||||
|
||||
def handle_event("keypress", %{"key" => "Enter"}, socket) do
|
||||
choices = socket.assigns.choices
|
||||
current = socket.assigns.higlighted
|
||||
|
||||
goal_name =
|
||||
Enum.find_value(choices, fn {id, goal_name} ->
|
||||
if id == current, do: goal_name
|
||||
end)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(%{show_picker?: false, value: goal_name, higlighted: nil})
|
||||
|> push_event("update-value", %{id: socket.assigns.id, value: goal_name})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("keypress", %{"key" => _other, "value" => typed}, socket) do
|
||||
if String.length(typed) > 0 do
|
||||
choices =
|
||||
Enum.filter(socket.assigns.options, fn {_id, goal_name} ->
|
||||
String.contains?(goal_name, typed) ||
|
||||
String.jaro_distance(String.upcase(goal_name), String.upcase(typed)) > 0.6
|
||||
end)
|
||||
|> Enum.sort_by(fn {_id, goal_name} -> String.jaro_distance(goal_name, typed) end, :desc)
|
||||
|> Enum.take(10)
|
||||
|
||||
first_id =
|
||||
case List.first(choices) do
|
||||
{id, _} -> id
|
||||
nil -> nil
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, %{choices: choices, show_picker?: true, higlighted: first_id})}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("hide-picker", _, socket) do
|
||||
{:noreply, assign(socket, %{show_picker?: false})}
|
||||
end
|
||||
|
||||
def handle_event("show-picker", _, socket) do
|
||||
socket =
|
||||
if socket.assigns[:choices] == [] do
|
||||
assign(socket, %{show_picker?: true, choices: socket.assigns.options})
|
||||
else
|
||||
assign(socket, %{show_picker?: true})
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event(event, payload, socket) do
|
||||
IO.inspect(event, label: :event)
|
||||
IO.inspect(payload, label: :payload)
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
@ -250,7 +250,7 @@ defmodule PlausibleWeb.Router do
|
||||
get "/:website/settings/funnels", SiteController, :settings_funnels
|
||||
# post "/:website/settings/funnels", SiteController, :save_funnel
|
||||
|
||||
# live "/:website/settings/funnels", Live.FunnelSettings
|
||||
# live "/:website/settings/funnel2", Live.FunnelSettings
|
||||
|
||||
get "/:website/settings/search-console", SiteController, :settings_search_console
|
||||
get "/:website/settings/email-reports", SiteController, :settings_email_reports
|
||||
|
@ -23,4 +23,7 @@
|
||||
<% end %>
|
||||
|
||||
<%= link("+ Add goal", to: "/#{URI.encode_www_form(@site.domain)}/goals/new", class: "button mt-6") %>
|
||||
<%= if Enum.count(@goals) >= 2 do %>
|
||||
<%= link("Set up funnels", to: Routes.site_path(@conn, :settings_funnels, @site.domain), class: "button mt-6 ml-2") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -4,12 +4,12 @@ defmodule Plausible.Repo.Migrations.InitFunnels do
|
||||
def change do
|
||||
create table(:funnels) do
|
||||
add :name, :string, null: false
|
||||
add :site_id, references(:sites), null: false
|
||||
add :site_id, references(:sites, on_delete: :delete_all), null: false
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create table(:funnel_steps) do
|
||||
add :goal_id, references(:goals), null: false
|
||||
add :goal_id, references(:goals, on_delete: :delete_all), null: false
|
||||
add :funnel_id, references(:funnels, on_delete: :delete_all), null: false
|
||||
add :step_order, :integer, null: false
|
||||
timestamps()
|
||||
|
Loading…
Reference in New Issue
Block a user