Deliver each drop to the zone under the cursor (#953)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-09-10 15:28:08 +02:00
1 parent c7b42bee96
commit eb65b16851
63 files changed
+1300 -554

No files matched your search

+4 -2
View File
@@ -2,7 +2,9 @@
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase
<div class="inner-scrolling-context">
@* The chat is a drop area: a file dropped anywhere in it hangs itself on the composer, which is
what users are used to. *@
<PathDropZone IsArea="@true" Class="inner-scrolling-context">
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-2" StretchItems="StretchItems.Start">
<MudText Typo="Typo.h3">
@@ -166,4 +168,4 @@
</MudDrawerContainer>
</MudDrawer>
}
</div>
</PathDropZone>
+10 -3
View File
@@ -4,7 +4,14 @@
@inherits MSGComponentBase
@attribute [Route(Routes.PLUGINS)]
<div class="inner-scrolling-context">
@* The page is its own drop target: a plugin archive may be dropped anywhere on it, and there is no
inner zone to hand that role to. An area which takes drops itself is that target by definition. *@
<PathDropZone IsArea="@true"
IdPrefix="plugins-page"
Class="inner-scrolling-context"
Disabled="@(() => !this.CanCatchDroppedFile())"
OnPathsDropped="@this.ImportDroppedPluginArchiveAsync"
Context="isDropTarget">
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-2">
<MudText Typo="Typo.h3">
@T("Plugins")
@@ -24,7 +31,7 @@
<InnerScrolling>
<MudTable Items="@PluginFactory.AvailablePlugins" Hover="@true" GroupBy="@this.groupConfig" Class="@this.PluginTableClass">
<MudTable Items="@PluginFactory.AvailablePlugins" Hover="@true" GroupBy="@this.groupConfig" Class="@PluginTableClass(isDropTarget)">
<ColGroup>
<col style="width: 2em;" />
<col style="width: 2.1em; "/>
@@ -153,4 +160,4 @@
</RowTemplate>
</MudTable>
</InnerScrolling>
</div>
</PathDropZone>
+6 -58
View File
@@ -42,14 +42,6 @@ public partial class Plugins : MSGComponentBase
private bool isSharingPlugin;
/// <summary>
/// Number of active drop areas above this page. While there is any, another component owns the
/// dropped files and this page must not catch them.
/// </summary>
private uint numDropAreasAboveThis;
private bool isDraggingOverPage;
private const string IMPORT_ICON =
@"<svg class=""mud-icon-root mud-svg-icon mud-dark-text mud-icon-size-medium"" focusable=""false"" viewBox=""0 0 24 24"" aria-hidden=""true"" role=""img"">
<path d=""M0 0h24v24H0V0z"" fill=""none""></path>
@@ -61,10 +53,7 @@ public partial class Plugins : MSGComponentBase
protected override async Task OnInitializedAsync()
{
this.ApplyFilters([], [ Event.PLUGINS_RELOADED, Event.CONFIGURATION_CHANGED, Event.TAURI_EVENT_RECEIVED, Event.REGISTER_FILE_DROP_AREA, Event.UNREGISTER_FILE_DROP_AREA ]);
// Register the whole page as a drop area, so users can drop a plugin archive anywhere on it:
await this.MessageBus.SendMessage(this, Event.REGISTER_FILE_DROP_AREA, DropLayers.PAGES);
this.ApplyFilters([], [ Event.PLUGINS_RELOADED, Event.CONFIGURATION_CHANGED ]);
this.groupConfig = new TableGroupDefinition<IPluginMetadata>
{
@@ -90,13 +79,6 @@ public partial class Plugins : MSGComponentBase
await this.TryAutoAuditAssistantsAsync();
}
protected override void DisposeResources()
{
// Release the drop area again, so lower layers can catch dropped files:
this.MessageBus.SendMessage(this, Event.UNREGISTER_FILE_DROP_AREA, DropLayers.PAGES).Observe($"{nameof(Plugins)}: releasing the drop area");
base.DisposeResources();
}
#endregion
private async Task PluginActivationStateChanged(IPluginMetadata pluginMeta)
@@ -276,7 +258,8 @@ public partial class Plugins : MSGComponentBase
/// Highlights the plugin table while the user drags a file over the page, so it is visible
/// where the file would land.
/// </summary>
private string PluginTableClass => this.isDraggingOverPage
/// <param name="isDropTarget">Whether the page is the target of the drop being aimed right now.</param>
private static string PluginTableClass(bool isDropTarget) => isDropTarget
? "border-dashed border rounded-lg mud-border-primary border-4"
: "border-dashed border rounded-lg";
@@ -571,51 +554,16 @@ public partial class Plugins : MSGComponentBase
case Event.CONFIGURATION_CHANGED:
await this.InvokeAsync(this.StateHasChanged);
break;
case Event.REGISTER_FILE_DROP_AREA when sendingComponent != this:
if (data is int registeredLayer && registeredLayer > DropLayers.PAGES)
this.numDropAreasAboveThis++;
break;
case Event.UNREGISTER_FILE_DROP_AREA when sendingComponent != this:
if (data is int unregisteredLayer && unregisteredLayer > DropLayers.PAGES && this.numDropAreasAboveThis > 0)
this.numDropAreasAboveThis--;
break;
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_HOVERED }:
if (!this.CanCatchDroppedFile())
return;
this.isDraggingOverPage = true;
await this.InvokeAsync(this.StateHasChanged);
break;
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_CANCELED }:
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.WINDOW_NOT_FOCUSED }:
this.isDraggingOverPage = false;
await this.InvokeAsync(this.StateHasChanged);
break;
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_DROPPED, Payload: var droppedPaths }:
this.isDraggingOverPage = false;
await this.InvokeAsync(this.StateHasChanged);
if (!this.CanCatchDroppedFile())
return;
await this.ImportDroppedPluginArchiveAsync(droppedPaths);
break;
}
}
#endregion
/// <summary>
/// Decides whether this page may process dropped files: only when no drop area above it is
/// active and when the organization allows importing plugins at all.
/// Decides whether this page may process dropped files: only when the organization allows
/// importing plugins at all and no import is running.
/// </summary>
private bool CanCatchDroppedFile() => this.numDropAreasAboveThis is 0 && this.AllowPluginImport && !this.isImportingAssistantPlugin;
private bool CanCatchDroppedFile() => this.AllowPluginImport && !this.isImportingAssistantPlugin;
/// <summary>
/// Imports a plugin archive the user dropped onto the page. Anything that is not exactly one