diff --git a/lib/bds/application.ex b/lib/bds/application.ex index 35391bb..05a7d31 100644 --- a/lib/bds/application.ex +++ b/lib/bds/application.ex @@ -34,6 +34,7 @@ defmodule BDS.Application do BDS.Preview, BDS.Publishing, {Task.Supervisor, name: BDS.Tasks.TaskSupervisor}, + {Task.Supervisor, name: BDS.TCP.TaskSupervisor}, BDS.Scripting.JobStore, {Task.Supervisor, name: BDS.Scripting.TaskSupervisor}, BDS.Scripting.JobSupervisor diff --git a/lib/bds/desktop/shell_live.ex b/lib/bds/desktop/shell_live.ex index ac86acf..168547e 100644 --- a/lib/bds/desktop/shell_live.ex +++ b/lib/bds/desktop/shell_live.ex @@ -23,6 +23,7 @@ defmodule BDS.Desktop.ShellLive do alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents alias BDS.Desktop.ShellLive.PostEditor alias BDS.Desktop.ShellLive.SidebarComponents, as: ShellSidebarComponents + alias BDS.Desktop.ShellLive.SidebarEvents alias BDS.Desktop.ShellLive.SidebarState, as: ShellSidebarState alias BDS.Desktop.ShellLive.{ @@ -61,6 +62,24 @@ defmodule BDS.Desktop.ShellLive do @refresh_interval 1_500 @output_entry_limit 20 + @sidebar_filter_events [ + "toggle_sidebar_filters", + "toggle_sidebar_archive", + "toggle_sidebar_tags", + "toggle_sidebar_categories", + "update_sidebar_search", + "clear_sidebar_search", + "clear_sidebar_tags", + "clear_sidebar_categories", + "toggle_sidebar_tag", + "toggle_sidebar_category", + "select_sidebar_year", + "select_sidebar_month", + "clear_sidebar_month", + "clear_sidebar_filters", + "load_more_sidebar" + ] + @local_menu_actions MapSet.new([ :toggle_sidebar, :toggle_panel, @@ -242,186 +261,8 @@ defmodule BDS.Desktop.ShellLive do {:noreply, reload_shell(socket, Layout.resize(socket.assigns.workbench, target, width))} end - def handle_event("toggle_sidebar_filters", _params, socket) do - socket = - ShellSidebarState.put_filter_panel_state(socket, fn state -> - if state.visible do - %{state | visible: false} - else - %{ - visible: true, - archive_collapsed: true, - tags_collapsed: true, - categories_collapsed: true, - expanded_year: nil - } - end - end) - - {:noreply, - socket - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("toggle_sidebar_archive", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filter_panel_state(fn state -> - %{state | archive_collapsed: not state.archive_collapsed} - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("toggle_sidebar_tags", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filter_panel_state(fn state -> - %{state | tags_collapsed: not state.tags_collapsed} - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("toggle_sidebar_categories", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filter_panel_state(fn state -> - %{state | categories_collapsed: not state.categories_collapsed} - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("update_sidebar_search", %{"sidebar_filters" => params}, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> - Map.put( - filters, - :search, - ShellSidebarState.normalize_filter_string(Map.get(params, "search")) - ) - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("clear_sidebar_search", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :search, nil) end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("clear_sidebar_tags", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :tags, []) end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("clear_sidebar_categories", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :categories, []) end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("toggle_sidebar_tag", %{"tag" => tag}, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> - ShellSidebarState.toggle_filter_value(filters, :tags, tag) - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("toggle_sidebar_category", %{"category" => category}, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> - ShellSidebarState.toggle_filter_value(filters, :categories, category) - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("select_sidebar_year", %{"year" => year}, socket) do - parsed_year = ShellSidebarState.parse_optional_integer(year) - - {:noreply, - socket - |> ShellSidebarState.put_filter_panel_state(fn state -> - %{ - state - | archive_collapsed: false, - expanded_year: if(state.expanded_year == parsed_year, do: nil, else: parsed_year) - } - end) - |> ShellSidebarState.put_filters(fn filters -> - filters - |> Map.put(:year, parsed_year) - |> Map.put(:month, nil) - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("select_sidebar_month", %{"year" => year, "month" => month}, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filter_panel_state(fn state -> - %{ - state - | archive_collapsed: false, - expanded_year: ShellSidebarState.parse_optional_integer(year) - } - end) - |> ShellSidebarState.put_filters(fn filters -> - filters - |> Map.put(:year, ShellSidebarState.parse_optional_integer(year)) - |> Map.put(:month, ShellSidebarState.parse_optional_integer(month)) - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("clear_sidebar_month", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filter_panel_state(fn state -> - %{state | archive_collapsed: false} - end) - |> ShellSidebarState.put_filters(fn filters -> - filters |> Map.put(:year, nil) |> Map.put(:month, nil) - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("clear_sidebar_filters", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> - filters - |> Map.put(:search, nil) - |> Map.put(:year, nil) - |> Map.put(:month, nil) - |> Map.put(:tags, []) - |> Map.put(:categories, []) - |> Map.put( - :display_limit, - ShellSidebarState.sidebar_page_size(socket.assigns.sidebar_data) - ) - end) - |> reload_shell(socket.assigns.workbench)} - end - - def handle_event("load_more_sidebar", _params, socket) do - {:noreply, - socket - |> ShellSidebarState.put_filters(fn filters -> - Map.update( - filters, - :display_limit, - ShellSidebarState.sidebar_page_size(socket.assigns.sidebar_data), - &(&1 + ShellSidebarState.sidebar_page_size(socket.assigns.sidebar_data)) - ) - end) - |> reload_shell(socket.assigns.workbench)} + def handle_event(event, params, socket) when event in @sidebar_filter_events do + SidebarEvents.handle(socket, event, params, &reload_shell/2) end def handle_event("create_sidebar_item", %{"kind" => kind}, socket) do diff --git a/lib/bds/desktop/shell_live/sidebar_events.ex b/lib/bds/desktop/shell_live/sidebar_events.ex new file mode 100644 index 0000000..4076f72 --- /dev/null +++ b/lib/bds/desktop/shell_live/sidebar_events.ex @@ -0,0 +1,189 @@ +defmodule BDS.Desktop.ShellLive.SidebarEvents do + @moduledoc false + + alias BDS.Desktop.ShellLive.SidebarState, as: ShellSidebarState + + @spec handle(Phoenix.LiveView.Socket.t(), String.t(), map(), (Phoenix.LiveView.Socket.t(), term() -> Phoenix.LiveView.Socket.t())) :: + {:noreply, Phoenix.LiveView.Socket.t()} + def handle(socket, event, params, reload) + + def handle(socket, "toggle_sidebar_filters", _params, reload) do + socket = + ShellSidebarState.put_filter_panel_state(socket, fn state -> + if state.visible do + %{state | visible: false} + else + %{ + visible: true, + archive_collapsed: true, + tags_collapsed: true, + categories_collapsed: true, + expanded_year: nil + } + end + end) + + {:noreply, reload.(socket, socket.assigns.workbench)} + end + + def handle(socket, "toggle_sidebar_archive", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filter_panel_state(fn state -> + %{state | archive_collapsed: not state.archive_collapsed} + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "toggle_sidebar_tags", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filter_panel_state(fn state -> + %{state | tags_collapsed: not state.tags_collapsed} + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "toggle_sidebar_categories", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filter_panel_state(fn state -> + %{state | categories_collapsed: not state.categories_collapsed} + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "update_sidebar_search", %{"sidebar_filters" => params}, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> + Map.put( + filters, + :search, + ShellSidebarState.normalize_filter_string(Map.get(params, "search")) + ) + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "clear_sidebar_search", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :search, nil) end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "clear_sidebar_tags", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :tags, []) end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "clear_sidebar_categories", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :categories, []) end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "toggle_sidebar_tag", %{"tag" => tag}, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> + ShellSidebarState.toggle_filter_value(filters, :tags, tag) + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "toggle_sidebar_category", %{"category" => category}, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> + ShellSidebarState.toggle_filter_value(filters, :categories, category) + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "select_sidebar_year", %{"year" => year}, reload) do + parsed_year = ShellSidebarState.parse_optional_integer(year) + + {:noreply, + socket + |> ShellSidebarState.put_filter_panel_state(fn state -> + %{ + state + | archive_collapsed: false, + expanded_year: if(state.expanded_year == parsed_year, do: nil, else: parsed_year) + } + end) + |> ShellSidebarState.put_filters(fn filters -> + filters + |> Map.put(:year, parsed_year) + |> Map.put(:month, nil) + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "select_sidebar_month", %{"year" => year, "month" => month}, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filter_panel_state(fn state -> + %{ + state + | archive_collapsed: false, + expanded_year: ShellSidebarState.parse_optional_integer(year) + } + end) + |> ShellSidebarState.put_filters(fn filters -> + filters + |> Map.put(:year, ShellSidebarState.parse_optional_integer(year)) + |> Map.put(:month, ShellSidebarState.parse_optional_integer(month)) + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "clear_sidebar_month", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filter_panel_state(fn state -> + %{state | archive_collapsed: false} + end) + |> ShellSidebarState.put_filters(fn filters -> + filters |> Map.put(:year, nil) |> Map.put(:month, nil) + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "clear_sidebar_filters", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> + filters + |> Map.put(:search, nil) + |> Map.put(:year, nil) + |> Map.put(:month, nil) + |> Map.put(:tags, []) + |> Map.put(:categories, []) + |> Map.put( + :display_limit, + ShellSidebarState.sidebar_page_size(socket.assigns.sidebar_data) + ) + end) + |> reload.(socket.assigns.workbench)} + end + + def handle(socket, "load_more_sidebar", _params, reload) do + {:noreply, + socket + |> ShellSidebarState.put_filters(fn filters -> + Map.update( + filters, + :display_limit, + ShellSidebarState.sidebar_page_size(socket.assigns.sidebar_data), + &(&1 + ShellSidebarState.sidebar_page_size(socket.assigns.sidebar_data)) + ) + end) + |> reload.(socket.assigns.workbench)} + end +end diff --git a/lib/bds/mcp/server.ex b/lib/bds/mcp/server.ex index 1cb5da0..86e76a0 100644 --- a/lib/bds/mcp/server.ex +++ b/lib/bds/mcp/server.ex @@ -69,6 +69,11 @@ defmodule BDS.MCP.Server do {:reply, response, state} end + @impl true + def handle_info(_msg, state) do + {:noreply, state} + end + defp ensure_started do case Process.whereis(__MODULE__) do nil -> @@ -83,7 +88,10 @@ defmodule BDS.MCP.Server do defp accept_loop(listener) do case :gen_tcp.accept(listener) do {:ok, socket} -> - spawn(fn -> serve_client(socket) end) + Task.Supervisor.start_child(BDS.TCP.TaskSupervisor, fn -> + serve_client(socket) + end) + accept_loop(listener) {:error, :closed} -> diff --git a/lib/bds/preview.ex b/lib/bds/preview.ex index fd8a585..776ff60 100644 --- a/lib/bds/preview.ex +++ b/lib/bds/preview.ex @@ -139,6 +139,11 @@ defmodule BDS.Preview do {:reply, response, state} end + @impl true + def handle_info(_msg, state) do + {:noreply, state} + end + defp ensure_running(%{project_id: project_id, is_running: true}, project_id), do: :ok defp ensure_running(_server, _project_id), do: {:error, :not_running} @@ -265,7 +270,10 @@ defmodule BDS.Preview do defp accept_loop(listener, project_id) do case :gen_tcp.accept(listener) do {:ok, socket} -> - spawn(fn -> serve_client(socket, project_id) end) + Task.Supervisor.start_child(BDS.TCP.TaskSupervisor, fn -> + serve_client(socket, project_id) + end) + accept_loop(listener, project_id) {:error, :closed} ->