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)