Compare commits

...

1 Commits

Author SHA1 Message Date
Lea Anthony 5f89fdea4b Improved window parameter handling 2024-05-05 18:44:37 +10:00
8 changed files with 127 additions and 56 deletions
+15 -6
View File
@@ -1,17 +1,26 @@
package main
import "github.com/wailsapp/wails/v3/examples/binding/data"
import (
"context"
"github.com/wailsapp/wails/v3/pkg/application"
)
// GreetService is a service that greets people
type GreetService struct {
}
// Greet greets a person
func (*GreetService) Greet(name string) string {
return "Hello " + name
func (*GreetService) Greet(win application.Window, name string) string {
return "Hello " + name + " on " + win.Name()
}
// GreetPerson greets a person
func (*GreetService) GreetPerson(person data.Person) string {
return "Hello " + person.Name
// GreetWithCtx greets a person
func (*GreetService) GreetWithCtx(ctx context.Context, name string) string {
win := ctx.Value("window").(application.Window)
return "[ctx] Hello " + name + " on " + win.Name()
}
// GreetWithCtx greets a person
func (*GreetService) GreetWithBoth(_ context.Context, win application.Window, name string) string {
return "[ctx+win] Hello " + name + " on " + win.Name()
}
@@ -1,28 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
// Person holds someone's most important attributes
export const Person = class {
/**
* Creates a new Person instance.
* @constructor
* @param {Object} source - The source object to create the Person.
* @param {string} source.Name
*/
constructor(source = {}) {
const { name = "" } = source;
this.name = name;
}
/**
* Creates a new Person instance from a string or object.
* @param {string|object} source - The source data to create a Person instance from.
* @returns {Person} A new Person instance.
*/
static createFrom(source) {
let parsedSource = typeof source === 'string' ? JSON.parse(source) : source;
return new Person(parsedSource);
}
};
@@ -19,10 +19,20 @@ export async function Greet(name) {
/**
* GreetPerson greets a person
* @function GreetPerson
* @param person {dataPerson}
* @function GreetWithCtx
* @param name {string}
* @returns {Promise<string>}
**/
export async function GreetPerson(person) {
return Call.ByName("main.GreetService.GreetPerson", ...Array.prototype.slice.call(arguments, 0));
export async function GreetWithCtx(name) {
return Call.ByName("main.GreetService.GreetWithCtx", ...Array.prototype.slice.call(arguments, 0));
}
/**
* GreetPerson greets a person
* @function GreetWithBoth
* @param name {string}
* @returns {Promise<string>}
**/
export async function GreetWithBoth(name) {
return Call.ByName("main.GreetService.GreetWithBoth", ...Array.prototype.slice.call(arguments, 0));
}
+12 -2
View File
@@ -83,9 +83,19 @@
<script type="module">
import * as GreetService from "./bindings/main/GreetService.js";
let state = 0;
async function greet() {
document.getElementById("result").innerHTML =
await GreetService.Greet(document.getElementById("name").value);
if(state % 3 === 0) {
document.getElementById("result").innerHTML =
await GreetService.Greet(document.getElementById("name").value);
} else if(state % 3 === 1) {
document.getElementById("result").innerHTML =
await GreetService.GreetWithCtx(document.getElementById("name").value);
} else {
document.getElementById("result").innerHTML =
await GreetService.GreetWithBoth(document.getElementById("name").value);
}
state++;
}
document.getElementById("greet-btn").onclick = greet;
+2
View File
@@ -26,6 +26,8 @@ func main() {
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
URL: "/",
Name: "Window 1",
Title: "Window 1",
DevToolsEnabled: true,
})
+19 -8
View File
@@ -308,7 +308,7 @@ func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod,
var errorType = reflect.TypeFor[error]()
// Call will attempt to call this bound method with the given args
func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (returnValue interface{}, err error) {
func (b *BoundMethod) Call(ctx context.Context, window Window, args []json.RawMessage) (returnValue interface{}, err error) {
// Use a defer statement to capture panics
defer func() {
if r := recover(); r != nil {
@@ -333,14 +333,8 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (returnV
argCount++
}
if argCount != len(b.Inputs) {
err = fmt.Errorf("%s expects %d arguments, received %d", b.Name, len(b.Inputs), argCount)
return
}
// Convert inputs to values of appropriate type
callArgs := make([]reflect.Value, argCount)
callArgs := make([]reflect.Value, len(b.Inputs))
base := 0
if b.needsContext {
@@ -348,6 +342,23 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (returnV
base++
}
firstArgIsWindow := len(b.Inputs) > 0 && b.Inputs[0].ReflectType == reflect.TypeFor[Window]()
secondArgIsWindow := len(b.Inputs) > 1 && b.Inputs[1].ReflectType == reflect.TypeFor[Window]()
if secondArgIsWindow && !b.needsContext {
return nil, fmt.Errorf("second argument is a Window but first argument is not a context")
}
if firstArgIsWindow || (b.needsContext && secondArgIsWindow) {
// Create a reflect.Value from the window interface
callArgs[base] = reflect.ValueOf(window)
base++
argCount++
}
if argCount != len(b.Inputs) {
err = fmt.Errorf("%s expects %d arguments, received %d", b.Name, len(b.Inputs), argCount)
return
}
// Iterate over given arguments
for index, arg := range args {
value := reflect.New(b.Inputs[base+index].ReflectType)
+64 -7
View File
@@ -52,6 +52,27 @@ func (t *TestService) Slice(a []int) []int {
return a
}
func (t *TestService) WithWindow(window application.Window, s string) string {
_ = window
return s
}
func (t *TestService) WithContext(ctx context.Context, s string) string {
_ = ctx
return s
}
func (t *TestService) WithContextAndWindow(ctx context.Context, window application.Window, s string) string {
_ = ctx
_ = window
return s
}
func (t *TestService) WithBadWindow(s string, window application.Window) string {
_ = window
return s
}
func newArgs(jsonArgs ...string) []json.RawMessage {
args := []json.RawMessage{}
@@ -64,11 +85,12 @@ func newArgs(jsonArgs ...string) []json.RawMessage {
func TestBoundMethodCall(t *testing.T) {
tests := []struct {
name string
method string
args []json.RawMessage
err error
expected interface{}
name string
method string
args []json.RawMessage
wantWindow bool
err error
expected interface{}
}{
{
name: "nil",
@@ -154,10 +176,41 @@ func TestBoundMethodCall(t *testing.T) {
err: nil,
expected: []int{1, 2, 3},
},
{
name: "with window",
method: "WithWindow",
args: newArgs(`"foo"`),
wantWindow: true,
err: nil,
expected: "foo",
},
{
name: "with context",
method: "WithContext",
args: newArgs(`"foo"`),
err: nil,
expected: "foo",
},
{
name: "with context and window",
method: "WithContextAndWindow",
args: newArgs(`"foo"`),
wantWindow: true,
err: nil,
expected: "foo",
},
{
name: "with bad window",
method: "WithBadWindow",
args: newArgs(`"foo"`),
wantWindow: true,
err: errors.New("second argument is a Window but first argument is not a context"),
expected: nil,
},
}
// init globalApplication
_ = application.New(application.Options{})
app := application.New(application.Options{})
bindings, err := application.NewBindings(
[]any{
@@ -180,8 +233,12 @@ func TestBoundMethodCall(t *testing.T) {
if method == nil {
t.Fatalf("bound method not found: %s", callOptions.Name())
}
var window application.Window
if tt.wantWindow {
window = app.NewWebviewWindow()
}
result, err := method.Call(context.TODO(), tt.args)
result, err := method.Call(context.TODO(), window, tt.args)
if tt.err != err && (tt.err == nil || err == nil || !strings.Contains(err.Error(), tt.err.Error())) {
t.Fatalf("error: %v, expected error: %v", err, tt.err)
}
+1 -1
View File
@@ -104,7 +104,7 @@ func (m *MessageProcessor) processCallMethod(method int, rw http.ResponseWriter,
m.l.Unlock()
}()
result, err := boundMethod.Call(ctx, options.Args)
result, err := boundMethod.Call(ctx, window, options.Args)
if err != nil {
m.callErrorCallback(window, "Error calling method: %s", callID, err)
return