fix: issue #18 more visible git activities

This commit is contained in:
2026-07-06 15:52:28 +02:00
parent 8ee51c6626
commit 10fa174388
16 changed files with 958 additions and 134 deletions

View File

@@ -14,6 +14,7 @@ defmodule BDS.Desktop.ShellLive do
ChatEditor,
GalleryImport,
GitHandler,
GitRun,
ImportEditor,
MediaEditor,
MenuEditor,
@@ -62,6 +63,7 @@ defmodule BDS.Desktop.ShellLive do
@refresh_interval 1_500
def refresh_interval, do: @refresh_interval
@sidebar_filter_events [
"toggle_sidebar_filters",
"toggle_sidebar_archive",
@@ -176,12 +178,13 @@ defmodule BDS.Desktop.ShellLive do
|> assign(:chat_editor_request_refs, %{})
|> assign(:file_picker_task, nil)
|> assign(:shell_overlay, nil)
|> assign(:git_run, nil)
|> assign(:output_entries, [])
|> assign(:panel_post_links, %{backlinks: [], outlinks: []})
|> assign(:panel_git_entries, [])
|> assign(:auto_save_timers, %{})
|> reload_shell(workbench)
|> UrlState.apply_params(params)
|> UrlState.apply_params(params)
|> tap(&sync_menu_bar_locale/1)}
end
@@ -209,7 +212,7 @@ defmodule BDS.Desktop.ShellLive do
{:noreply,
socket
|> refresh_sidebar(workbench)
|> UrlState.push()}
|> UrlState.push()}
end
def handle_event("select_panel_tab", %{"tab" => tab}, socket) do
@@ -242,9 +245,25 @@ defmodule BDS.Desktop.ShellLive do
end
def handle_event(event, _params, socket) when event in @git_action_events do
{:noreply, GitHandler.run_action(socket, event)}
if GitRun.network_event?(event) do
{:noreply, GitRun.start(socket, event, current_project_id(socket))}
else
{:noreply, GitHandler.run_action(socket, event)}
end
end
def handle_event("git_run_close", _params, socket) do
socket = GitRun.close(socket)
{:noreply, refresh_sidebar(socket, socket.assigns.workbench)}
end
def handle_event("git_run_keydown", %{"key" => "Escape"}, socket) do
socket = GitRun.close(socket)
{:noreply, refresh_sidebar(socket, socket.assigns.workbench)}
end
def handle_event("git_run_keydown", _params, socket), do: {:noreply, socket}
def handle_event("git_commit", params, socket) do
message = params |> get_in(["git", "message"]) |> to_string() |> String.trim()
@@ -302,7 +321,7 @@ defmodule BDS.Desktop.ShellLive do
socket
|> assign(:tab_meta, tab_meta)
|> refresh_layout(workbench)
|> UrlState.push()}
|> UrlState.push()}
end
def handle_event(
@@ -691,6 +710,15 @@ defmodule BDS.Desktop.ShellLive do
{:noreply, handle_blogmark_deep_link(socket, url)}
end
def handle_info({:git_output, ref, stream, chunk}, socket) do
{:noreply, GitRun.append(socket, ref, stream, chunk)}
end
def handle_info({:git_done, ref, status}, socket) do
socket = GitRun.done(socket, ref, status)
{:noreply, refresh_sidebar(socket, socket.assigns.workbench)}
end
def handle_info(message, socket) do
Bridges.handle_info(message, socket, bridges_callbacks())
end
@@ -987,7 +1015,8 @@ defmodule BDS.Desktop.ShellLive do
defp handle_socket_menu_action(socket, :save), do: TabActions.save_current_tab(socket)
defp handle_socket_menu_action(socket, :publish_selected), do: TabActions.publish_current_tab(socket)
defp handle_socket_menu_action(socket, :publish_selected),
do: TabActions.publish_current_tab(socket)
defp handle_socket_menu_action(socket, :quit) do
Shutdown.request_quit()

View File

@@ -0,0 +1,130 @@
defmodule BDS.Desktop.ShellLive.GitRun do
@moduledoc """
Live modal for streaming network git commands (fetch/pull/push).
Starts a `BDS.Git.Stream`, appends its stdout/stderr chunks as they arrive
(stderr rendered in red), shows a spinner while running and a pass/fail banner
from the exit status. Closing the modal cancels a still-running command, which
kills the OS process.
"""
use Phoenix.Component
use Gettext, backend: BDS.Gettext
alias BDS.Git
@operations %{"git_fetch" => :fetch, "git_pull" => :pull, "git_push" => :push}
@doc "True when the shell event is a streaming network git action."
def network_event?(event), do: Map.has_key?(@operations, event)
@doc "Open the modal and start streaming the operation."
def start(socket, event, project_id) do
operation = Map.fetch!(@operations, event)
run =
case start_stream(project_id, operation) do
{:ok, pid, ref} ->
%{operation: operation, pid: pid, ref: ref, lines: [], status: :running}
{:error, reason} ->
%{
operation: operation,
pid: nil,
ref: nil,
lines: [{:stderr, error_text(reason)}],
status: {:done, 1}
}
end
assign(socket, :git_run, run)
end
@doc "Append a streamed chunk to the modal (newest first internally)."
def append(socket, ref, stream, chunk) do
case socket.assigns[:git_run] do
%{ref: ^ref, lines: lines} = run ->
assign(socket, :git_run, %{run | lines: [{stream, chunk} | lines]})
_other ->
socket
end
end
@doc "Mark the run finished with its exit status."
def done(socket, ref, status) do
case socket.assigns[:git_run] do
%{ref: ^ref} = run ->
assign(socket, :git_run, %{run | status: {:done, status}, pid: nil})
_other ->
socket
end
end
@doc "Close the modal, cancelling (killing) a still-running command."
def close(socket) do
case socket.assigns[:git_run] do
%{status: :running, pid: pid} when is_pid(pid) -> Git.Stream.cancel(pid)
_other -> :ok
end
assign(socket, :git_run, nil)
end
defp start_stream(nil, _operation), do: {:error, :no_project}
defp start_stream("default", _operation), do: {:error, :no_project}
defp start_stream(project_id, operation) when is_binary(project_id),
do: Git.stream(project_id, operation, self())
defp error_text(reason) when reason in [:no_project, :not_found],
do: dgettext("ui", "No active project") <> "\n"
defp error_text(reason), do: inspect(reason) <> "\n"
def git_run_modal(assigns) do
~H"""
<%= if @git_run do %>
<div class="shell-overlay-backdrop git-run-backdrop" data-testid="git-run-backdrop" phx-window-keydown="git_run_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="git_run_close" aria-label={dgettext("ui", "Close")}></button>
<div class="git-run-modal" role="dialog" aria-modal="true">
<div class="git-run-header">
<h2 class="git-run-title">{title(@git_run.operation)}</h2>
<span class={["git-run-status", status_class(@git_run.status)]} data-testid="git-run-status">
{status_label(@git_run.status)}
</span>
</div>
<pre class="git-run-output" data-testid="git-run-output"><%= for {stream, text} <- Enum.reverse(@git_run.lines) do %><span class={line_class(stream)}>{text}</span><% end %></pre>
<div class="git-run-footer">
<button class="git-action-button" type="button" phx-click="git_run_close" data-testid="git-run-close">
{close_label(@git_run.status)}
</button>
</div>
</div>
</div>
<% end %>
"""
end
defp title(:fetch), do: dgettext("ui", "git fetch")
defp title(:pull), do: dgettext("ui", "git pull")
defp title(:push), do: dgettext("ui", "git push")
defp line_class(:stderr), do: "git-run-line git-run-stderr"
defp line_class(:stdout), do: "git-run-line git-run-stdout"
defp status_class(:running), do: "running"
defp status_class({:done, 0}), do: "ok"
defp status_class({:done, _status}), do: "fail"
defp status_label(:running), do: dgettext("ui", "Running…")
defp status_label({:done, 0}), do: dgettext("ui", "Done")
defp status_label({:done, status}),
do: dgettext("ui", "Failed (exit %{status})", status: status)
defp close_label(:running), do: dgettext("ui", "Cancel")
defp close_label({:done, _status}), do: dgettext("ui", "Close")
end

View File

@@ -678,4 +678,5 @@
</footer>
<ShellOverlayComponents.shell_overlay shell_overlay={@shell_overlay} />
<GitRun.git_run_modal git_run={@git_run} />
</div>