From 23bf0302d32691da9028dd435ec5033c3639598e Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 13 Mar 2026 14:36:55 +0000 Subject: [PATCH] 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 --- pkg/window/messages.go | 6 ++++++ pkg/window/mock_platform.go | 4 ++++ pkg/window/mock_test.go | 11 +++++++++++ pkg/window/platform.go | 3 +++ pkg/window/service.go | 7 +++++++ pkg/window/service_test.go | 37 +++++++++++++++++++++++++++++++++++++ pkg/window/wails.go | 12 ++++++++++++ 7 files changed, 80 insertions(+) diff --git a/pkg/window/messages.go b/pkg/window/messages.go index b999e07..f663b4b 100644 --- a/pkg/window/messages.go +++ b/pkg/window/messages.go @@ -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"` +} diff --git a/pkg/window/mock_platform.go b/pkg/window/mock_platform.go index 3317ada..f460d77 100644 --- a/pkg/window/mock_platform.go +++ b/pkg/window/mock_platform.go @@ -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) +} diff --git a/pkg/window/mock_test.go b/pkg/window/mock_test.go index 1329772..4b989ef 100644 --- a/pkg/window/mock_test.go +++ b/pkg/window/mock_test.go @@ -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) + } +} diff --git a/pkg/window/platform.go b/pkg/window/platform.go index 7db1319..f8e62ee 100644 --- a/pkg/window/platform.go +++ b/pkg/window/platform.go @@ -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. diff --git a/pkg/window/service.go b/pkg/window/service.go index d038400..85286e5 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -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 { diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index 05997ac..1911044 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -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() +} diff --git a/pkg/window/wails.go b/pkg/window/wails.go index ef2ac51..7d9fbb1 100644 --- a/pkg/window/wails.go +++ b/pkg/window/wails.go @@ -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)