mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 19:50:15 +08:00
Reorder startup sequence
Add plugin scripts to asset server Add InjectJS support in plugins
This commit is contained in:
parent
bf86b0d9c1
commit
5949e305ea
@ -2,8 +2,11 @@ package assetserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
@ -42,6 +45,9 @@ type AssetServer struct {
|
||||
// Use http based runtime
|
||||
runtimeHandler RuntimeHandler
|
||||
|
||||
// plugin scripts
|
||||
pluginScripts map[string]string
|
||||
|
||||
assetServerWebView
|
||||
}
|
||||
|
||||
@ -89,6 +95,16 @@ func (d *AssetServer) UseRuntimeHandler(handler RuntimeHandler) {
|
||||
d.runtimeHandler = handler
|
||||
}
|
||||
|
||||
func (d *AssetServer) AddPluginScript(pluginName string, script string) {
|
||||
if d.pluginScripts == nil {
|
||||
d.pluginScripts = make(map[string]string)
|
||||
}
|
||||
pluginName = strings.ReplaceAll(pluginName, "/", "_")
|
||||
pluginName = html.EscapeString(pluginName)
|
||||
pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000))
|
||||
d.pluginScripts[pluginScriptName] = script
|
||||
}
|
||||
|
||||
func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if isWebSocket(req) {
|
||||
// Forward WebSockets to the distinct websocket handler if it exists
|
||||
@ -149,6 +165,11 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
d.writeBlob(rw, path, content)
|
||||
|
||||
default:
|
||||
// Check if this is a plugin script
|
||||
if script, ok := d.pluginScripts[path]; ok {
|
||||
d.writeBlob(rw, path, []byte(script))
|
||||
return
|
||||
}
|
||||
d.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
@ -174,6 +195,13 @@ func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Inject plugins
|
||||
for scriptName := range d.pluginScripts {
|
||||
if err := insertScriptInHead(htmlNode, scriptName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = html.Render(&buffer, htmlNode)
|
||||
if err != nil {
|
||||
|
@ -108,3 +108,75 @@ This attribute specifies which javascript event should trigger the action. The d
|
||||
<button data-wml-event="hover-box" data-wml-trigger="mouseover">Hover over me!</button>
|
||||
```
|
||||
|
||||
## Plugins
|
||||
|
||||
Plugins are a way to extend the functionality of your Wails application.
|
||||
|
||||
### Creating a plugin
|
||||
|
||||
Plugins are standard Go structure that adhere to the following interface:
|
||||
|
||||
```go
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Init(*application.App) error
|
||||
Shutdown()
|
||||
CallableByJS() []string
|
||||
InjectJS() string
|
||||
}
|
||||
```
|
||||
|
||||
The `Name()` method returns the name of the plugin. This is used for logging purposes.
|
||||
|
||||
The `Init(*application.App) error` method is called when the plugin is loaded. The `*application.App`
|
||||
parameter is the application that the plugin is being loaded into. Any errors will prevent
|
||||
the application from starting.
|
||||
|
||||
The `Shutdown()` method is called when the application is shutting down.
|
||||
|
||||
The `CallableByJS()` method returns a list of exported functions that can be called from
|
||||
the frontend. These method names must exactly match the names of the methods exported
|
||||
by the plugin.
|
||||
|
||||
The `InjectJS()` method returns JavaScript that should be injected into all windows as they are created. This is useful for adding custom JavaScript functions that complement the plugin.
|
||||
|
||||
### Tips
|
||||
|
||||
#### Enums
|
||||
|
||||
In Go, enums are often defined as a type and a set of constants. For example:
|
||||
|
||||
```go
|
||||
type MyEnum int
|
||||
|
||||
const (
|
||||
MyEnumOne MyEnum = iota
|
||||
MyEnumTwo
|
||||
MyEnumThree
|
||||
)
|
||||
```
|
||||
|
||||
Due to incompatibility between Go and JavaScript, custom types cannot be used in this way. The best strategy is to use a type alias for float64:
|
||||
|
||||
```go
|
||||
type MyEnum = float64
|
||||
|
||||
const (
|
||||
MyEnumOne MyEnum = iota
|
||||
MyEnumTwo
|
||||
MyEnumThree
|
||||
)
|
||||
```
|
||||
|
||||
In Javascript, you can then use the following:
|
||||
|
||||
```js
|
||||
const MyEnum = {
|
||||
MyEnumOne: 0,
|
||||
MyEnumTwo: 1,
|
||||
MyEnumThree: 2
|
||||
}
|
||||
```
|
||||
|
||||
- Why use `float64`? Can't we use `int`?
|
||||
- Because JavaScript doesn't have a concept of `int`. Everything is a `number`, which translates to `float64` in Go. There are also restrictions on casting types in Go's reflection package, which means using `int` doesn't work.
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
@ -32,6 +33,10 @@ func (r *Plugin) CallableByJS() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Plugin) InjectJS() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ---------------- Plugin Methods ----------------
|
||||
|
||||
type Hashes struct {
|
||||
|
@ -38,6 +38,7 @@ func New(appOptions Options) *App {
|
||||
log: logger.New(appOptions.Logger.CustomLoggers...),
|
||||
contextMenus: make(map[string]*Menu),
|
||||
}
|
||||
globalApplication = result
|
||||
|
||||
if !appOptions.Logger.Silent {
|
||||
result.log.AddOutput(&logger.Console{})
|
||||
@ -60,7 +61,24 @@ func New(appOptions Options) *App {
|
||||
srv.UseRuntimeHandler(NewMessageProcessor())
|
||||
result.assets = srv
|
||||
|
||||
globalApplication = result
|
||||
result.bindings, err = NewBindings(appOptions.Bind)
|
||||
if err != nil {
|
||||
println("Fatal error in application initialisation: ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
err = result.bindings.AddPlugins(appOptions.Plugins)
|
||||
if err != nil {
|
||||
println("Fatal error in application initialisation: ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
result.plugins = NewPluginManager(appOptions.Plugins, srv)
|
||||
err = result.plugins.Init()
|
||||
if err != nil {
|
||||
println("Fatal error in application initialisation: ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -318,20 +336,6 @@ func (a *App) Run() error {
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
a.bindings, err = NewBindings(a.options.Bind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.plugins = NewPluginManager(a.options.Plugins)
|
||||
err = a.plugins.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.bindings.AddPlugins(a.options.Plugins)
|
||||
|
||||
// run windows
|
||||
for _, window := range a.windows {
|
||||
go window.run()
|
||||
@ -348,7 +352,7 @@ func (a *App) Run() error {
|
||||
// set the application Icon
|
||||
a.impl.setIcon(a.options.Icon)
|
||||
|
||||
err = a.impl.run()
|
||||
err := a.impl.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -2,10 +2,11 @@ package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/samber/lo"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CallOptions struct {
|
||||
@ -174,7 +175,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
|
||||
if isFunction(value) {
|
||||
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
|
||||
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
|
||||
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct", name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not a pointer to a struct")
|
||||
|
@ -1,5 +1,7 @@
|
||||
package application
|
||||
|
||||
import "github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Init(app *App) error
|
||||
@ -10,13 +12,14 @@ type Plugin interface {
|
||||
|
||||
type PluginManager struct {
|
||||
plugins map[string]Plugin
|
||||
assetServer *assetserver.AssetServer
|
||||
}
|
||||
|
||||
func NewPluginManager(plugins map[string]Plugin) *PluginManager {
|
||||
func NewPluginManager(plugins map[string]Plugin, assetServer *assetserver.AssetServer) *PluginManager {
|
||||
result := &PluginManager{
|
||||
plugins: plugins,
|
||||
assetServer: assetServer,
|
||||
}
|
||||
globalApplication.OnWindowCreation(result.onWindowCreation)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -28,7 +31,7 @@ func (p *PluginManager) Init() error {
|
||||
}
|
||||
injectJS := plugin.InjectJS()
|
||||
if injectJS != "" {
|
||||
|
||||
p.assetServer.AddPluginScript(plugin.Name(), injectJS)
|
||||
}
|
||||
globalApplication.info("Plugin '%s' initialised", plugin.Name())
|
||||
}
|
||||
@ -41,12 +44,3 @@ func (p *PluginManager) Shutdown() {
|
||||
globalApplication.info("Plugin '%s' shutdown", plugin.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PluginManager) onWindowCreation(window *WebviewWindow) {
|
||||
for _, plugin := range p.plugins {
|
||||
injectJS := plugin.InjectJS()
|
||||
if injectJS != "" {
|
||||
window.ExecJS(injectJS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
# Plugins
|
||||
|
||||
Plugins are a way to extend the functionality of your Wails application.
|
||||
|
||||
## Creating a plugin
|
||||
|
||||
Plugins are standard Go structure that adhere to the following interface:
|
||||
|
||||
```go
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Init(*application.App) error
|
||||
Shutdown()
|
||||
CallableByJS() []string
|
||||
}
|
||||
```
|
||||
|
||||
The `Name()` method returns the name of the plugin. This is used for logging purposes.
|
||||
|
||||
The `Init(*application.App) error` method is called when the plugin is loaded. The `*application.App`
|
||||
parameter is the application that the plugin is being loaded into. Any errors will prevent
|
||||
the application from starting.
|
||||
|
||||
The `Shutdown()` method is called when the application is shutting down.
|
||||
|
||||
The `CallableByJS()` method returns a list of exported functions that can be called from
|
||||
the frontend. These method names must exactly match the names of the methods exported
|
||||
by the plugin.
|
||||
|
@ -1,3 +0,0 @@
|
||||
# TODO
|
||||
|
||||
- [ ] Add InjectCSS() to the plugin API?
|
@ -33,6 +33,10 @@ func (p *Plugin) CallableByJS() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Plugin) InjectJS() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ---------------- Plugin Methods ----------------
|
||||
|
||||
func (p *Plugin) OpenURL(url string) error {
|
||||
|
@ -2,11 +2,12 @@ package kvstore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/logger"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/logger"
|
||||
)
|
||||
|
||||
type KeyValueStore struct {
|
||||
@ -18,6 +19,10 @@ type KeyValueStore struct {
|
||||
app *application.App
|
||||
}
|
||||
|
||||
func (kvs *KeyValueStore) InjectJS() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Filename string
|
||||
AutoSave bool
|
||||
@ -67,6 +72,10 @@ func (kvs *KeyValueStore) CallableByJS() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Plugin) InjectJS() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ---------------- Plugin Methods ----------------
|
||||
|
||||
func (kvs *KeyValueStore) open(filename string) (err error) {
|
||||
|
@ -1,18 +1,23 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed plugin.js
|
||||
var pluginJS string
|
||||
|
||||
// ---------------- 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 LogLevel int
|
||||
type LogLevel = float64
|
||||
|
||||
const (
|
||||
Trace LogLevel = iota + 1
|
||||
@ -55,6 +60,7 @@ func NewPluginWithConfig(config *Config) *Plugin {
|
||||
}
|
||||
return &Plugin{
|
||||
config: config,
|
||||
level: config.Level,
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,9 +94,14 @@ func (p *Plugin) CallableByJS() []string {
|
||||
"Warning",
|
||||
"Error",
|
||||
"Fatal",
|
||||
"SetLevel",
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Plugin) InjectJS() string {
|
||||
return pluginJS
|
||||
}
|
||||
|
||||
// ---------------- Plugin Methods ----------------
|
||||
// Plugin methods are just normal Go methods. You can add as many as you like.
|
||||
// The only requirement is that they are exported (start with a capital letter).
|
||||
@ -98,7 +109,7 @@ func (p *Plugin) CallableByJS() []string {
|
||||
// See https://golang.org/pkg/encoding/json/#Marshal for more information.
|
||||
|
||||
func (p *Plugin) write(prefix string, level LogLevel, message string, args ...any) {
|
||||
if level >= p.config.Level {
|
||||
if level >= p.level {
|
||||
if !p.config.DisablePrefix {
|
||||
message = prefix + " " + message
|
||||
}
|
||||
@ -134,5 +145,8 @@ func (p *Plugin) Fatal(message string, args ...any) {
|
||||
}
|
||||
|
||||
func (p *Plugin) SetLevel(newLevel LogLevel) {
|
||||
if newLevel == 0 {
|
||||
newLevel = Debug
|
||||
}
|
||||
p.level = newLevel
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
* @param args {...any} - The arguments for the log message.
|
||||
* @returns {Promise<void|Error>}
|
||||
*/
|
||||
export function Trace(input, ...args) {
|
||||
function Trace(input, ...args) {
|
||||
return wails.Plugin("log", "Trace", input, ...args);
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export function Trace(input, ...args) {
|
||||
* @returns {Promise<void|Error>}
|
||||
*/
|
||||
|
||||
export function Debug(input, ...args) {
|
||||
function Debug(input, ...args) {
|
||||
return wails.Plugin("log", "Debug", input, ...args);
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ export function Debug(input, ...args) {
|
||||
* @param args {...any} - The arguments for the log message.
|
||||
* @returns {Promise<void|Error>}
|
||||
*/
|
||||
export function Info(input, ...args) {
|
||||
function Info(input, ...args) {
|
||||
return wails.Plugin("log", "Info", input, ...args);
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export function Info(input, ...args) {
|
||||
* @param args {...any} - The arguments for the log message.
|
||||
* @returns {Promise<void|Error>}
|
||||
*/
|
||||
export function Warning(input, ...args) {
|
||||
function Warning(input, ...args) {
|
||||
return wails.Plugin("log", "Warning", input, ...args);
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ export function Warning(input, ...args) {
|
||||
* @param args {...any} - The arguments for the log message.
|
||||
* @returns {Promise<void|Error>}
|
||||
*/
|
||||
export function Error(input, ...args) {
|
||||
function Error(input, ...args) {
|
||||
return wails.Plugin("log", "Error", input, ...args);
|
||||
}
|
||||
|
||||
@ -59,6 +59,40 @@ export function Error(input, ...args) {
|
||||
* @param args {...any} - The arguments for the log message.
|
||||
* @returns {Promise<void|Error>}
|
||||
*/
|
||||
export function Fatal(input, ...args) {
|
||||
function Fatal(input, ...args) {
|
||||
return wails.Plugin("log", "Fatal", input, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetLevel sets the logging level
|
||||
* @param level {Level} The log level to set
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function SetLevel(level) {
|
||||
return wails.Plugin("log", "SetLevel", level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log Level.
|
||||
* @readonly
|
||||
* @enum {number}
|
||||
*/
|
||||
let Level = {
|
||||
Trace: 1,
|
||||
Debug: 2,
|
||||
Info: 3,
|
||||
Warning: 4,
|
||||
Error: 5,
|
||||
Fatal: 6,
|
||||
};
|
||||
|
||||
window.Logger = {
|
||||
Trace,
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal,
|
||||
SetLevel,
|
||||
Level,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user