From ba82f2753494389f68ca3789dba5b39fbdb454ca Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 20 Mar 2023 20:28:33 +1100 Subject: [PATCH] Fix Bug with nil parameters. Added browser and kvstore plugins. --- v3/TODO.md | 7 +- v3/examples/plugins/go.mod | 2 + v3/examples/plugins/go.sum | 5 ++ v3/examples/plugins/main.go | 11 ++- v3/examples/plugins/store.json | 1 + v3/pkg/application/bindings.go | 8 +- v3/plugins/browser/README.md | 51 +++++++++++++ v3/plugins/browser/plugin.go | 37 +++++++++ v3/plugins/browser/plugin.js | 25 ++++++ v3/plugins/browser/plugin.toml | 11 +++ v3/plugins/kvstore/README.md | 66 ++++++++++++++++ v3/plugins/kvstore/kvstore.go | 135 +++++++++++++++++++++++++++++++++ v3/plugins/kvstore/plugin.js | 31 ++++++++ v3/plugins/kvstore/plugin.toml | 11 +++ 14 files changed, 396 insertions(+), 5 deletions(-) create mode 100644 v3/examples/plugins/store.json create mode 100644 v3/plugins/browser/README.md create mode 100644 v3/plugins/browser/plugin.go create mode 100644 v3/plugins/browser/plugin.js create mode 100644 v3/plugins/browser/plugin.toml create mode 100644 v3/plugins/kvstore/README.md create mode 100644 v3/plugins/kvstore/kvstore.go create mode 100644 v3/plugins/kvstore/plugin.js create mode 100644 v3/plugins/kvstore/plugin.toml diff --git a/v3/TODO.md b/v3/TODO.md index 1652fb1e6..eb099a96d 100644 --- a/v3/TODO.md +++ b/v3/TODO.md @@ -41,4 +41,9 @@ Informal and incomplete list of things needed in v3. ## Runtime - [ ] To log or not to log? -- [ ] Unify cross-platform events, eg. `onClose` \ No newline at end of file +- [ ] Unify cross-platform events, eg. `onClose` + +## Plugins + +- [ ] Move logins to `v3/plugins` +- [ ] Expose application logger to plugins \ No newline at end of file diff --git a/v3/examples/plugins/go.mod b/v3/examples/plugins/go.mod index b71c21d6f..c63f0f96b 100644 --- a/v3/examples/plugins/go.mod +++ b/v3/examples/plugins/go.mod @@ -9,11 +9,13 @@ require ( github.com/leaanthony/slicer v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect ) replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/plugins/go.sum b/v3/examples/plugins/go.sum index c06e0dbc6..91099da09 100644 --- a/v3/examples/plugins/go.sum +++ b/v3/examples/plugins/go.sum @@ -11,6 +11,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= @@ -27,6 +29,9 @@ golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/v3/examples/plugins/main.go b/v3/examples/plugins/main.go index 4678a8488..7b62d0bae 100644 --- a/v3/examples/plugins/main.go +++ b/v3/examples/plugins/main.go @@ -2,10 +2,12 @@ package main import ( "embed" - _ "embed" "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/browser" + "github.com/wailsapp/wails/v3/plugins/kvstore" "log" "plugin_demo/plugins/hashes" + //"plugin_demo/plugins/hashes" ) //go:embed assets/* @@ -20,7 +22,12 @@ func main() { ApplicationShouldTerminateAfterLastWindowClosed: true, }, Plugins: map[string]application.Plugin{ - "hashes": hashes.NewPlugin(), + "hashes": hashes.NewPlugin(), + "browser": browser.NewPlugin(), + "kvstore": kvstore.NewPlugin(&kvstore.Config{ + Filename: "store.json", + AutoSave: true, + }), }, Assets: application.AssetOptions{ FS: assets, diff --git a/v3/examples/plugins/store.json b/v3/examples/plugins/store.json new file mode 100644 index 000000000..6a107caa1 --- /dev/null +++ b/v3/examples/plugins/store.json @@ -0,0 +1 @@ +{"url":null,"url2":"https://reddit.com"} \ No newline at end of file diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go index ed155079e..3e97ee28a 100644 --- a/v3/pkg/application/bindings.go +++ b/v3/pkg/application/bindings.go @@ -30,14 +30,14 @@ var reservedPluginMethods = []string{ type Parameter struct { Name string `json:"name,omitempty"` TypeName string `json:"type"` - reflectType reflect.Type + ReflectType reflect.Type } func newParameter(Name string, Type reflect.Type) *Parameter { return &Parameter{ Name: Name, TypeName: Type.String(), - reflectType: Type, + ReflectType: Type, } } @@ -243,6 +243,10 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) { // Iterate over given arguments for index, arg := range args { // Save the converted argument + if arg == nil { + callArgs[index] = reflect.Zero(b.Inputs[index].ReflectType) + continue + } callArgs[index] = reflect.ValueOf(arg) } diff --git a/v3/plugins/browser/README.md b/v3/plugins/browser/README.md new file mode 100644 index 000000000..f5103d33b --- /dev/null +++ b/v3/plugins/browser/README.md @@ -0,0 +1,51 @@ +# Browser Plugin + +This plugin provides the ability to open a URL or local file in the default browser. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/browser" +) + +func main() { + browserPlugin := browser.NewPlugin() + app := application.New(application.Options{ + // ... + Plugins: map[string]application.Plugin{ + "browser": browserPlugin, + }, + }) +``` + +## Usage + +### Go + +You can call the methods exported by the plugin directly: + +```go + browserPlugin.OpenURL("https://www.google.com") + // or + browserPlugin.OpenFile("/path/to/file") +``` + +### Javascript + +You can call the methods from the frontend using the Plugin method: + +```js + wails.Plugin("browser","OpenURL","https://www.google.com") + // or + wails.Plugin("browser","OpenFile","/path/to/file") +``` + +## Support + +If you find a bug in this plugin, please raise a ticket on the Wails [Issue Tracker](https://github.com/wailsapp/wails/issues). diff --git a/v3/plugins/browser/plugin.go b/v3/plugins/browser/plugin.go new file mode 100644 index 000000000..dc0dfa164 --- /dev/null +++ b/v3/plugins/browser/plugin.go @@ -0,0 +1,37 @@ +package browser + +import ( + "github.com/pkg/browser" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type Plugin struct{} + +func NewPlugin() *Plugin { + return &Plugin{} +} + +func (p *Plugin) Shutdown() {} + +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/browser" +} + +func (p *Plugin) Init(_ *application.App) error { + return nil +} + +// ---------------- Plugin Methods ---------------- + +func (p *Plugin) OpenURL(url string) error { + return browser.OpenURL(url) +} + +func (p *Plugin) OpenFile(path string) error { + return browser.OpenFile(path) +} diff --git a/v3/plugins/browser/plugin.js b/v3/plugins/browser/plugin.js new file mode 100644 index 000000000..d2f304fa3 --- /dev/null +++ b/v3/plugins/browser/plugin.js @@ -0,0 +1,25 @@ + +/** + * Opens the given URL in the default browser. + * @param url {string} - The URL to open. + * @returns {Promise} + */ +function OpenURL(url) { + return wails.Plugin("browser", "OpenURL", url); +} + +/** + * Opens the given filename in the default browser. + * @param filename {string} - The file to open. + * @returns {Promise} + */ +function OpenFile(filename) { + return wails.Plugin("browser", "OpenFile", filename); +} + +export default { + Browser: { + OpenURL, + OpenFile, + } +}; diff --git a/v3/plugins/browser/plugin.toml b/v3/plugins/browser/plugin.toml new file mode 100644 index 000000000..47e547389 --- /dev/null +++ b/v3/plugins/browser/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "browser" plugin. + +Name = "browser" +Description = "Opens URLs and files in the default browser." +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +Repository = "https://github.com/wailsapp/wails/v3/plugins/browser" +License = "MIT" + + diff --git a/v3/plugins/kvstore/README.md b/v3/plugins/kvstore/README.md new file mode 100644 index 000000000..bb6dd0f07 --- /dev/null +++ b/v3/plugins/kvstore/README.md @@ -0,0 +1,66 @@ +# KVStore Plugin + +This plugin provides a simple key/value store for your Wails applications. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/kvstore" +) + +func main() { + kvstorePlugin := kvstore.NewPlugin(&kvstore.Config{ + Filename: "myapp.db", + }) + app := application.New(application.Options{ + // ... + Plugins: map[string]application.Plugin{ + "kvstore": kvstorePlugin, + }, + }) + +``` + +## Usage + +### Go + +You can call the methods exported by the plugin directly: + +```go + err := kvstore.Set("url", "https://www.google.com") + if err != nil { + // handle error + } + url := kvstore.Get("url").(string) + + // If you have not enables AutoSave, you will need to call Save() to persist the changes + err = kvstore.Save() + if err != nil { + // handle error + } +``` + +### Javascript + +You can call the methods from the frontend using the Plugin method: + +```js + wails.Plugin("kvstore","Set", "url", "https://www.google.com") + wails.Plugin("kvstore","Get", "url").then((url) => { + + }) + + // or + wails.Plugin("browser","OpenFile","/path/to/file") +``` + +## Support + +If you find a bug in this plugin, please raise a ticket on the Wails [Issue Tracker](https://github.com/wailsapp/wails/issues). diff --git a/v3/plugins/kvstore/kvstore.go b/v3/plugins/kvstore/kvstore.go new file mode 100644 index 000000000..2e345b157 --- /dev/null +++ b/v3/plugins/kvstore/kvstore.go @@ -0,0 +1,135 @@ +package kvstore + +import ( + "encoding/json" + "github.com/wailsapp/wails/v3/pkg/application" + "io" + "os" + "sync" +) + +type KeyValueStore struct { + config *Config + filename string + data map[string]any + unsaved bool + lock sync.RWMutex +} + +type Config struct { + Filename string + AutoSave bool +} + +type Plugin struct{} + +func NewPlugin(config *Config) *KeyValueStore { + return &KeyValueStore{ + config: config, + data: make(map[string]any), + } +} + +// Shutdown will save the store to disk if there are unsaved changes. +func (kvs *KeyValueStore) Shutdown() { + if kvs.unsaved { + err := kvs.Save() + if err != nil { + println("Error saving store: " + err.Error()) + } + } +} + +// Name returns the name of the plugin. +func (kvs *KeyValueStore) Name() string { + return "github.com/wailsapp/wails/v3/plugins/kvstore" +} + +// Init is called when the plugin is loaded. It is passed the application.App +// instance. This is where you should do any setup. +func (kvs *KeyValueStore) Init(_ *application.App) error { + err := kvs.open(kvs.config.Filename) + if err != nil { + return err + } + + return nil +} + +// ---------------- Plugin Methods ---------------- + +func (kvs *KeyValueStore) open(filename string) error { + kvs.filename = filename + kvs.data = make(map[string]any) + + file, err := os.Open(filename) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer file.Close() + + bytes, err := io.ReadAll(file) + if err != nil { + return err + } + + if len(bytes) > 0 { + if err := json.Unmarshal(bytes, &kvs.data); err != nil { + return err + } + } + + return nil +} + +// Save saves the store to disk +func (kvs *KeyValueStore) Save() error { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + bytes, err := json.Marshal(kvs.data) + if err != nil { + return err + } + + err = os.WriteFile(kvs.filename, bytes, 0644) + if err != nil { + return err + } + + kvs.unsaved = false + + return nil +} + +// Get returns the value for the given key. If key is empty, the entire store is returned. +func (kvs *KeyValueStore) Get(key string) any { + kvs.lock.RLock() + defer kvs.lock.RUnlock() + + if key == "" { + return kvs.data + } + + return kvs.data[key] +} + +// Set sets the value for the given key. If AutoSave is true, the store is saved to disk. +func (kvs *KeyValueStore) Set(key string, value any) error { + kvs.lock.Lock() + kvs.data[key] = value + kvs.lock.Unlock() + if kvs.config.AutoSave { + err := kvs.Save() + if err != nil { + return err + } + kvs.unsaved = false + } else { + kvs.unsaved = true + } + return nil +} diff --git a/v3/plugins/kvstore/plugin.js b/v3/plugins/kvstore/plugin.js new file mode 100644 index 000000000..1b2faec49 --- /dev/null +++ b/v3/plugins/kvstore/plugin.js @@ -0,0 +1,31 @@ +// plugin.js +// This file should contain helper functions for the that can be used by the frontend. +// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions. + +/** + * Get the value of a key. + * @param key {string} - The store key. + * @returns {Promise} - The value of the key. + */ +export function Get(key) { + return wails.Plugin("kvstore", "Get", key); +} + +/** + * Set the value of a key. + @param key {string} - The store key. + @param value {any} - The value to set. + * @returns {Promise} + */ +export function Set(key, value) { + return wails.Plugin("kvstore", "Set", key, value); +} + + +/** + * Save the database to disk. + * @returns {Promise} + */ +export function Save() { + return wails.Plugin("kvstore", "Save"); +} diff --git a/v3/plugins/kvstore/plugin.toml b/v3/plugins/kvstore/plugin.toml new file mode 100644 index 000000000..cebd76c8a --- /dev/null +++ b/v3/plugins/kvstore/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "kvstore" plugin. + +Name = "kvstore" +Description = "A Simple Key/Value Store for Wails Applications" +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +Repository = "https://github.com/wailsapp/wails/v3/plugins/kvstore" +License = "MIT" + +