mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-19 11:03:38 +00:00
Made dragging the splitter in the chat smooth (#978)
Some checks are pending
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Some checks are pending
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
parent
0993517608
commit
558819031a
@ -60,6 +60,22 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase
|
||||
|
||||
private bool IsDirectoryAvailable => this.directoryInfo.Exists;
|
||||
|
||||
/// <summary>
|
||||
/// Takes the next file which the directory scan found.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This runs on the scan's own thread, and it does so deliberately, although the scan asks its
|
||||
/// callers to reach for a dispatcher. That request is about updating the UI, and none of these
|
||||
/// callbacks does: they write fields and nothing else.<br/><br/>
|
||||
/// Why that holds is worth writing down, because the code does not show it. The string builder
|
||||
/// has exactly one writer -- this method, on that one thread -- and nobody else ever reads it.
|
||||
/// What the renderer reads is the text field beside it, and assigning a string reference is
|
||||
/// atomic, so a render sees the whole previous text or the whole new one, never half of either.
|
||||
/// Building that text anew costs little, because the scan stops reporting files once it has
|
||||
/// reported a hundred. And a render happens only when the refresh timer ticks, which goes
|
||||
/// through the dispatcher, so a reading taken a moment too early is replaced 1.6 seconds later
|
||||
/// anyway.
|
||||
/// </remarks>
|
||||
private void UpdateFileList(string file)
|
||||
{
|
||||
this.directoryFiles.Append("- ");
|
||||
@ -67,13 +83,38 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase
|
||||
this.directoryFilesText = this.directoryFiles.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the size which the directory scan has added up so far.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Two threads call this, but never at the same time: the scan reports its progress from its
|
||||
/// own thread, and the final figure follows once that thread has finished. A long is written in
|
||||
/// one piece on all six targets we ship, which are 64 bit throughout, and the renderer reads it
|
||||
/// only when the refresh timer ticks. The remark on the file list carries the reasoning these
|
||||
/// callbacks share.
|
||||
/// </remarks>
|
||||
private void UpdateDirectorySize(long size)
|
||||
{
|
||||
this.directorySizeBytes = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the number of files which the directory scan has counted so far.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reported from the same two threads as the size above, and safe for the same reason.
|
||||
/// </remarks>
|
||||
private void UpdateDirectoryFiles(long numFiles) => this.directorySizeNumFiles = numFiles;
|
||||
|
||||
/// <summary>
|
||||
/// Takes the news that the directory scan has finished.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This one, unlike the three above, does not run on the scan's thread. The scan invokes it
|
||||
/// after awaiting its worker, and that continuation returns to the dispatcher this dialog was
|
||||
/// initialized on. Stopping the timer and asking for a render from here is therefore no
|
||||
/// different from doing either in a lifecycle method.
|
||||
/// </remarks>
|
||||
private void DirectoryOperationDone()
|
||||
{
|
||||
this.refreshTimer.Stop();
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
</CascadingValue>
|
||||
@if (this.AreWorkspacesVisible)
|
||||
{
|
||||
<MudSplitter Dimension="@this.ReadSplitterPosition" DimensionChanged="this.SplitterChanged" EnableSlide="@this.AreWorkspacesVisible" EnableMargin="@false" StartContentStyle="margin-right: 1em;" BarStyle="" EndContentStyle="margin-left: 1em;">
|
||||
<MudSplitter Dimension="@this.ReadSplitterPosition" DimensionChanged="this.SplitterChanged" Sensitivity="0.05" EnableSlide="@this.AreWorkspacesVisible" EnableMargin="@false" StartContentStyle="margin-right: 1em;" BarStyle="" EndContentStyle="margin-left: 1em;">
|
||||
<StartContent>
|
||||
@if (this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_SIDEBAR && this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible)
|
||||
{
|
||||
|
||||
@ -27,6 +27,7 @@ public partial class Chat : MSGComponentBase
|
||||
private string currentWorkspaceName = string.Empty;
|
||||
private Workspaces? workspaces;
|
||||
private double splitterPosition = 30;
|
||||
private bool skipRenderAfterSplitterChange;
|
||||
private readonly ChatComposerState composerState = new();
|
||||
|
||||
private readonly Timer splitterSaveTimer = new(TimeSpan.FromSeconds(1.6));
|
||||
@ -39,6 +40,16 @@ public partial class Chat : MSGComponentBase
|
||||
|
||||
this.splitterPosition = this.SettingsManager.ConfigurationData.Workspace.SplitterPosition;
|
||||
this.splitterSaveTimer.AutoReset = false;
|
||||
//
|
||||
// Mind that this handler deliberately stays off the renderer thread, although it writes the
|
||||
// configuration data from a thread pool thread. The position is a single double, and every
|
||||
// target we ship is 64 bit, so the write cannot tear -- and the worst a lost one could do is
|
||||
// a splitter standing somewhere else after the next start. What a jump to the dispatcher
|
||||
// would cost instead is paid by the user: storing the settings serializes all of them and
|
||||
// writes two files, and it would do that in the very queue which draws the drag they are in
|
||||
// the middle of. The splitter then stutters under their hand. Whoever synchronizes the
|
||||
// configuration data one day should do it without moving that work onto the renderer.
|
||||
//
|
||||
this.splitterSaveTimer.Elapsed += (_, _) =>
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.Workspace.SplitterPosition = this.splitterPosition;
|
||||
@ -47,7 +58,31 @@ public partial class Chat : MSGComponentBase
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decides whether this page renders again.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Dragging the splitter reports every movement, and Blazor renders this page after each of
|
||||
/// them. That render is pure waste: all it would contribute is the position the splitter just
|
||||
/// reported, and the splitter has it already -- it renders itself after its own event, which is
|
||||
/// what resizes the two panels. What this page rebuilds instead is everything else it holds,
|
||||
/// the workspace tree above all, which has no render guard of its own and draws an item with
|
||||
/// three buttons for every chat. That is what the user sees stutter while they drag.<br/><br/>
|
||||
/// Dropping that one render costs nothing, because the splitter never needed it. Should a
|
||||
/// message from the bus ask for a render in the very same moment, this swallows it -- both sit
|
||||
/// on the same dispatcher and the render of a movement follows it without a gap, so the window
|
||||
/// is as good as closed, and the next render brings the message along anyway.
|
||||
/// </remarks>
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
if (!this.skipRenderAfterSplitterChange)
|
||||
return true;
|
||||
|
||||
this.skipRenderAfterSplitterChange = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string WorkspaceSidebarToggleIcon => this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible ? Icons.Material.Filled.ArrowCircleLeft : Icons.Material.Filled.ArrowCircleRight;
|
||||
@ -75,6 +110,7 @@ public partial class Chat : MSGComponentBase
|
||||
this.splitterPosition = position;
|
||||
this.splitterSaveTimer.Stop();
|
||||
this.splitterSaveTimer.Start();
|
||||
this.skipRenderAfterSplitterChange = true;
|
||||
}
|
||||
|
||||
private void ToggleWorkspacesOverlay()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user