Wire notification categories and layout helpers
This commit is contained in:
parent
0fb1ef5cfb
commit
ba4780f2f8
3 changed files with 117 additions and 0 deletions
|
|
@ -1464,6 +1464,82 @@ func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// LayoutBesideEditor places a window beside a detected editor window.
|
||||
//
|
||||
// result, err := svc.LayoutBesideEditor("preview", "code", "right", 0.62)
|
||||
func (s *Service) LayoutBesideEditor(name, editor, side string, ratio float64) (window.LayoutBesideEditorResult, error) {
|
||||
r := s.Core().Action("window.layoutBesideEditor").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: window.TaskLayoutBesideEditor{
|
||||
Name: name, Editor: editor, Side: side, Ratio: ratio,
|
||||
}},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return window.LayoutBesideEditorResult{}, e
|
||||
}
|
||||
return window.LayoutBesideEditorResult{}, nil
|
||||
}
|
||||
result, _ := r.Value.(window.LayoutBesideEditorResult)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LayoutSuggest returns a layout recommendation for the current screen.
|
||||
//
|
||||
// suggestion, err := svc.LayoutSuggest("", 2)
|
||||
func (s *Service) LayoutSuggest(screenID string, windowCount int) (window.LayoutSuggestion, error) {
|
||||
r := s.Core().Action("window.layoutSuggest").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: window.TaskLayoutSuggest{
|
||||
ScreenID: screenID, WindowCount: windowCount,
|
||||
}},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return window.LayoutSuggestion{}, e
|
||||
}
|
||||
return window.LayoutSuggestion{}, nil
|
||||
}
|
||||
result, _ := r.Value.(window.LayoutSuggestion)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindScreenSpace finds an unused rectangle for a new window.
|
||||
//
|
||||
// space, err := svc.FindScreenSpace("", 800, 600, 24)
|
||||
func (s *Service) FindScreenSpace(screenID string, width, height, padding int) (window.ScreenSpace, error) {
|
||||
r := s.Core().Action("window.findSpace").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: window.TaskScreenFindSpace{
|
||||
ScreenID: screenID, Width: width, Height: height, Padding: padding,
|
||||
}},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return window.ScreenSpace{}, e
|
||||
}
|
||||
return window.ScreenSpace{}, nil
|
||||
}
|
||||
result, _ := r.Value.(window.ScreenSpace)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ArrangeWindowPair positions two windows in an optimal split.
|
||||
//
|
||||
// arrangement, err := svc.ArrangeWindowPair("editor", "preview", "", 0.55)
|
||||
func (s *Service) ArrangeWindowPair(primary, secondary, screenID string, ratio float64) (window.PairArrangement, error) {
|
||||
r := s.Core().Action("window.arrangePair").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: window.TaskWindowArrangePair{
|
||||
Primary: primary, Secondary: secondary, ScreenID: screenID, Ratio: ratio,
|
||||
}},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return window.PairArrangement{}, e
|
||||
}
|
||||
return window.PairArrangement{}, nil
|
||||
}
|
||||
result, _ := r.Value.(window.PairArrangement)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetEventManager returns the event manager for WebSocket event subscriptions.
|
||||
func (s *Service) GetEventManager() *WSEventManager {
|
||||
return s.events
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ func (s *Service) send(options NotificationOptions) error {
|
|||
if options.ID == "" {
|
||||
options.ID = "core-" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
||||
options = s.applyCategoryActions(options)
|
||||
|
||||
if err := s.platform.Send(options); err != nil {
|
||||
// Fallback: show as dialog via IPC
|
||||
|
|
@ -115,6 +116,22 @@ func (s *Service) send(options NotificationOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) applyCategoryActions(options NotificationOptions) NotificationOptions {
|
||||
if options.CategoryID == "" || len(options.Actions) > 0 {
|
||||
return options
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
category, ok := s.categories[options.CategoryID]
|
||||
s.mu.Unlock()
|
||||
if !ok || len(category.Actions) == 0 {
|
||||
return options
|
||||
}
|
||||
|
||||
options.Actions = append([]NotificationAction(nil), category.Actions...)
|
||||
return options
|
||||
}
|
||||
|
||||
// fallbackDialog shows a dialog via IPC when native notifications fail.
|
||||
func (s *Service) fallbackDialog(options NotificationOptions) error {
|
||||
// Map severity to dialog type
|
||||
|
|
|
|||
|
|
@ -216,6 +216,30 @@ func TestTaskSend_WithActions_Good(t *testing.T) {
|
|||
assert.Equal(t, 2, len(mock.lastOpts.Actions))
|
||||
}
|
||||
|
||||
func TestTaskSend_RegisteredCategoryActions_Good(t *testing.T) {
|
||||
mock, c := newTestService(t)
|
||||
require.True(t, taskRun(c, "notification.registerCategory", TaskRegisterCategory{
|
||||
Category: NotificationCategory{
|
||||
ID: "message",
|
||||
Actions: []NotificationAction{
|
||||
{ID: "reply", Title: "Reply"},
|
||||
{ID: "dismiss", Title: "Dismiss"},
|
||||
},
|
||||
},
|
||||
}).OK)
|
||||
|
||||
r := taskRun(c, "notification.send", TaskSend{
|
||||
Options: NotificationOptions{
|
||||
Title: "Team Chat",
|
||||
Message: "New message from Alice",
|
||||
CategoryID: "message",
|
||||
},
|
||||
})
|
||||
require.True(t, r.OK)
|
||||
assert.Equal(t, 2, len(mock.lastOpts.Actions))
|
||||
assert.Equal(t, "reply", mock.lastOpts.Actions[0].ID)
|
||||
}
|
||||
|
||||
func TestTaskClear_Good_Specific(t *testing.T) {
|
||||
mock, c := newTestService(t)
|
||||
require.True(t, taskRun(c, "notification.send", TaskSend{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue