feat(window): add file drop support to PlatformWindow interface

Adds OnFileDrop(handler func(paths []string, targetID string)) to PlatformWindow.
trackWindow() now wires file drop callbacks to ActionFilesDropped broadcasts.
Updates both exported MockWindow and unexported mockWindow with the new method.
Wails adapter maps WindowFilesDropped event with DroppedFiles and DropTargetDetails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-03-13 14:36:55 +00:00
parent 1e0b89b94e
commit 23bf0302d3
7 changed files with 80 additions and 0 deletions

View file

@ -72,3 +72,9 @@ type ActionWindowResized struct {
type ActionWindowFocused struct{ Name string }
type ActionWindowBlurred struct{ Name string }
type ActionFilesDropped struct {
Name string `json:"name"` // window name
Paths []string `json:"paths"`
TargetID string `json:"targetId,omitempty"`
}

View file

@ -35,6 +35,7 @@ type MockWindow struct {
visible, alwaysOnTop bool
closed bool
eventHandlers []func(WindowEvent)
fileDropHandlers []func(paths []string, targetID string)
}
func (w *MockWindow) Name() string { return w.name }
@ -58,3 +59,6 @@ func (w *MockWindow) Hide() { w.visible = fals
func (w *MockWindow) Fullscreen() {}
func (w *MockWindow) UnFullscreen() {}
func (w *MockWindow) OnWindowEvent(handler func(WindowEvent)) { w.eventHandlers = append(w.eventHandlers, handler) }
func (w *MockWindow) OnFileDrop(handler func(paths []string, targetID string)) {
w.fileDropHandlers = append(w.fileDropHandlers, handler)
}

View file

@ -34,6 +34,7 @@ type mockWindow struct {
visible, alwaysOnTop bool
closed bool
eventHandlers []func(WindowEvent)
fileDropHandlers []func(paths []string, targetID string)
}
func (w *mockWindow) Name() string { return w.name }
@ -57,6 +58,9 @@ func (w *mockWindow) Hide() { w.visible = fals
func (w *mockWindow) Fullscreen() {}
func (w *mockWindow) UnFullscreen() {}
func (w *mockWindow) OnWindowEvent(handler func(WindowEvent)) { w.eventHandlers = append(w.eventHandlers, handler) }
func (w *mockWindow) OnFileDrop(handler func(paths []string, targetID string)) {
w.fileDropHandlers = append(w.fileDropHandlers, handler)
}
// emit fires a test event to all registered handlers.
func (w *mockWindow) emit(e WindowEvent) {
@ -64,3 +68,10 @@ func (w *mockWindow) emit(e WindowEvent) {
h(e)
}
}
// emitFileDrop simulates a file drop on the window.
func (w *mockWindow) emitFileDrop(paths []string, targetID string) {
for _, h := range w.fileDropHandlers {
h(paths, targetID)
}
}

View file

@ -56,6 +56,9 @@ type PlatformWindow interface {
// Events
OnWindowEvent(handler func(event WindowEvent))
// File drop
OnFileDrop(handler func(paths []string, targetID string))
}
// WindowEvent is emitted by the backend for window state changes.

View file

@ -168,6 +168,13 @@ func (s *Service) trackWindow(pw PlatformWindow) {
_ = s.Core().ACTION(ActionWindowClosed{Name: e.Name})
}
})
pw.OnFileDrop(func(paths []string, targetID string) {
_ = s.Core().ACTION(ActionFilesDropped{
Name: pw.Name(),
Paths: paths,
TargetID: targetID,
})
})
}
func (s *Service) taskCloseWindow(name string) error {

View file

@ -2,6 +2,7 @@ package window
import (
"context"
"sync"
"testing"
"forge.lthn.ai/core/go/pkg/core"
@ -137,3 +138,39 @@ func TestTaskMaximise_Good(t *testing.T) {
info := result.(*WindowInfo)
assert.True(t, info.Maximized)
}
func TestFileDrop_Good(t *testing.T) {
_, c := newTestWindowService(t)
// Open a window
result, _, _ := c.PERFORM(TaskOpenWindow{
Opts: []WindowOption{WithName("drop-test")},
})
info := result.(WindowInfo)
assert.Equal(t, "drop-test", info.Name)
// Capture broadcast actions
var dropped ActionFilesDropped
var mu sync.Mutex
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if a, ok := msg.(ActionFilesDropped); ok {
mu.Lock()
dropped = a
mu.Unlock()
}
return nil
})
// Get the mock window and simulate file drop
svc := core.MustServiceFor[*Service](c, "window")
pw, ok := svc.Manager().Get("drop-test")
require.True(t, ok)
mw := pw.(*mockWindow)
mw.emitFileDrop([]string{"/tmp/file1.txt", "/tmp/file2.txt"}, "upload-zone")
mu.Lock()
assert.Equal(t, "drop-test", dropped.Name)
assert.Equal(t, []string{"/tmp/file1.txt", "/tmp/file2.txt"}, dropped.Paths)
assert.Equal(t, "upload-zone", dropped.TargetID)
mu.Unlock()
}

View file

@ -120,6 +120,18 @@ func (ww *wailsWindow) OnWindowEvent(handler func(event WindowEvent)) {
}
}
func (ww *wailsWindow) OnFileDrop(handler func(paths []string, targetID string)) {
ww.w.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) {
files := event.Context().DroppedFiles()
details := event.Context().DropTargetDetails()
targetID := ""
if details != nil {
targetID = details.ElementID
}
handler(files, targetID)
})
}
// Ensure wailsWindow satisfies PlatformWindow at compile time.
var _ PlatformWindow = (*wailsWindow)(nil)