feat: major refactor

This commit is contained in:
Lea Anthony
2019-07-12 10:12:15 +10:00
parent caa1e04b5a
commit 8aa97f64ef
72 changed files with 9221 additions and 980 deletions

View File

@@ -1,42 +0,0 @@
{{ if .Versions -}}
<a name="unreleased"></a>
## [Unreleased]
{{ if .Unreleased.CommitGroups -}}
{{ range .Unreleased.CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ range .Versions }}
<a name="{{ .Tag.Name }}"></a>
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
{{ range .CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
{{ end }}
{{ end -}}
{{- if .NoteGroups -}}
{{ range .NoteGroups -}}
### {{ .Title }}
{{ range .Notes }}
{{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{- if .Versions }}
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
{{ range .Versions -}}
{{ if .Tag.Previous -}}
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
{{ end -}}
{{ end -}}
{{ end -}}

View File

@@ -1,27 +0,0 @@
style: github
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: https://github.com/wailsapp/wails
options:
commits:
# filters:
# Type:
# - feat
# - fix
# - perf
# - refactor
commit_groups:
# title_maps:
# feat: Features
# fix: Bug Fixes
# perf: Performance Improvements
# refactor: Code Refactoring
header:
pattern: "^(\\w*)\\:\\s(.*)$"
pattern_maps:
- Type
- Subject
notes:
keywords:
- BREAKING CHANGE

2
.gitignore vendored
View File

@@ -16,4 +16,4 @@ examples/**/example*
cmd/wails/wails cmd/wails/wails
.DS_Store .DS_Store
tmp tmp
dist node_modules

55
app.go
View File

@@ -2,6 +2,13 @@ package wails
import ( import (
"github.com/wailsapp/wails/cmd" "github.com/wailsapp/wails/cmd"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/runtime/go/runtime"
"github.com/wailsapp/wails/lib/renderer"
"github.com/wailsapp/wails/lib/binding"
"github.com/wailsapp/wails/lib/ipc"
"github.com/wailsapp/wails/lib/event"
"github.com/wailsapp/wails/lib/interfaces"
) )
// -------------------------------- Compile time Flags ------------------------------ // -------------------------------- Compile time Flags ------------------------------
@@ -13,35 +20,35 @@ var BuildMode = cmd.BuildModeProd
// App defines the main application struct // App defines the main application struct
type App struct { type App struct {
config *AppConfig // The Application configuration object config *Config // The Application configuration object
cli *cmd.Cli // In debug mode, we have a cli cli *cmd.Cli // In debug mode, we have a cli
renderer Renderer // The renderer is what we will render the app to renderer interfaces.Renderer // The renderer is what we will render the app to
logLevel string // The log level of the app logLevel string // The log level of the app
ipc *ipcManager // Handles the IPC calls ipc interfaces.IPCManager // Handles the IPC calls
log *CustomLogger // Logger log *logger.CustomLogger // Logger
bindingManager *bindingManager // Handles binding of Go code to renderer bindingManager interfaces.BindingManager // Handles binding of Go code to renderer
eventManager *eventManager // Handles all the events eventManager interfaces.EventManager // Handles all the events
runtime *Runtime // The runtime object for registered structs runtime interfaces.Runtime // The runtime object for registered structs
} }
// CreateApp creates the application window with the given configuration // CreateApp creates the application window with the given configuration
// If none given, the defaults are used // If none given, the defaults are used
func CreateApp(optionalConfig ...*AppConfig) *App { func CreateApp(optionalConfig ...*Config) *App {
var userConfig *AppConfig var userConfig *Config
if len(optionalConfig) > 0 { if len(optionalConfig) > 0 {
userConfig = optionalConfig[0] userConfig = optionalConfig[0]
} }
result := &App{ result := &App{
logLevel: "info", logLevel: "info",
renderer: &webViewRenderer{}, renderer: renderer.NewWebView(),
ipc: newIPCManager(), ipc: ipc.NewManager(),
bindingManager: newBindingManager(), bindingManager: binding.NewManager(),
eventManager: newEventManager(), eventManager: event.NewManager(),
log: newCustomLogger("App"), log: logger.NewCustomLogger("App"),
} }
appconfig, err := newAppConfig(userConfig) appconfig, err := newConfig(userConfig)
if err != nil { if err != nil {
result.log.Fatalf("Cannot use custom HTML: %s", err.Error()) result.log.Fatalf("Cannot use custom HTML: %s", err.Error())
} }
@@ -75,14 +82,14 @@ func (a *App) Run() error {
func (a *App) start() error { func (a *App) start() error {
// Set the log level // Set the log level
setLogLevel(a.logLevel) logger.SetLogLevel(a.logLevel)
// Log starup // Log starup
a.log.Info("Starting") a.log.Info("Starting")
// Check if we are to run in headless mode // Check if we are to run in headless mode
if BuildMode == cmd.BuildModeBridge { if BuildMode == cmd.BuildModeBridge {
a.renderer = &Headless{} a.renderer = &renderer.Headless{}
} }
// Initialise the renderer // Initialise the renderer
@@ -92,16 +99,16 @@ func (a *App) start() error {
} }
// Start event manager and give it our renderer // Start event manager and give it our renderer
a.eventManager.start(a.renderer) a.eventManager.Start(a.renderer)
// Start the IPC Manager and give it the event manager and binding manager // Start the IPC Manager and give it the event manager and binding manager
a.ipc.start(a.eventManager, a.bindingManager) a.ipc.Start(a.eventManager, a.bindingManager)
// Create the runtime // Create the runtime
a.runtime = newRuntime(a.eventManager, a.renderer) a.runtime = runtime.NewRuntime(a.eventManager, a.renderer)
// Start binding manager and give it our renderer // Start binding manager and give it our renderer
err = a.bindingManager.start(a.renderer, a.runtime) err = a.bindingManager.Start(a.renderer, a.runtime)
if err != nil { if err != nil {
return err return err
} }
@@ -113,5 +120,5 @@ func (a *App) start() error {
// Bind allows the user to bind the given object // Bind allows the user to bind the given object
// with the application // with the application
func (a *App) Bind(object interface{}) { func (a *App) Bind(object interface{}) {
a.bindingManager.bind(object) a.bindingManager.Bind(object)
} }

View File

@@ -1,97 +0,0 @@
package wails
import (
"strings"
"github.com/dchest/htmlmin"
"github.com/leaanthony/mewn"
)
// AppConfig is the configuration structure used when creating a Wails App object
type AppConfig struct {
Width, Height int
Title string
defaultHTML string
HTML string
JS string
CSS string
Colour string
Resizable bool
DisableInspector bool
isHTMLFragment bool
}
func (a *AppConfig) merge(in *AppConfig) error {
if in.CSS != "" {
a.CSS = in.CSS
}
if in.Title != "" {
a.Title = in.Title
}
if in.HTML != "" {
minified, err := htmlmin.Minify([]byte(in.HTML), &htmlmin.Options{
MinifyScripts: true,
})
if err != nil {
return err
}
inlineHTML := string(minified)
inlineHTML = strings.Replace(inlineHTML, "'", "\\'", -1)
inlineHTML = strings.Replace(inlineHTML, "\n", " ", -1)
a.HTML = strings.TrimSpace(inlineHTML)
// Deduce whether this is a full html page or a fragment
// The document is determined to be a fragment if an HTML
// tag exists and is located before the first div tag
HTMLTagIndex := strings.Index(a.HTML, "<html")
DivTagIndex := strings.Index(a.HTML, "<div")
if HTMLTagIndex == -1 {
a.isHTMLFragment = true
} else {
if DivTagIndex < HTMLTagIndex {
a.isHTMLFragment = true
}
}
}
if in.Colour != "" {
a.Colour = in.Colour
}
if in.JS != "" {
a.JS = in.JS
}
if in.Width != 0 {
a.Width = in.Width
}
if in.Height != 0 {
a.Height = in.Height
}
a.Resizable = in.Resizable
a.DisableInspector = in.DisableInspector
return nil
}
// Creates the default configuration
func newAppConfig(userConfig *AppConfig) (*AppConfig, error) {
result := &AppConfig{
Width: 800,
Height: 600,
Resizable: true,
Title: "My Wails App",
Colour: "#FFF", // White by default
HTML: mewn.String("./wailsruntimeassets/default/default.html"),
}
if userConfig != nil {
err := result.merge(userConfig)
if err != nil {
return nil, err
}
}
return result, nil
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -271,7 +271,7 @@ func InstallBridge(caller string, projectDir string, projectOptions *ProjectOpti
} }
// Copy bridge to project // Copy bridge to project
bridgeAssets := mewn.Group("../wailsruntimeassets/bridge/") bridgeAssets := mewn.Group("../runtime/bridge/")
bridgeFileData := bridgeAssets.Bytes(bridgeFile) bridgeFileData := bridgeAssets.Bytes(bridgeFile)
bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, projectOptions.FrontEnd.Bridge, "wailsbridge.js") bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, projectOptions.FrontEnd.Bridge, "wailsbridge.js")
err := fs.CreateFile(bridgeFileTarget, bridgeFileData) err := fs.CreateFile(bridgeFileTarget, bridgeFileData)

View File

@@ -1,4 +1,4 @@
package cmd package cmd
// Version - Wails version // Version - Wails version
const Version = "v0.17.2-pre" const Version = "v0.17.3-pre"

142
config.go Normal file
View File

@@ -0,0 +1,142 @@
package wails
import (
"strings"
"github.com/dchest/htmlmin"
// "github.com/leaanthony/mewn"
)
// Config is the configuration structure used when creating a Wails App object
type Config struct {
Width, Height int
Title string
defaultHTML string
HTML string
JS string
CSS string
Colour string
Resizable bool
DisableInspector bool
// isHTMLFragment bool
}
// GetWidth returns the desired width
func (a *Config) GetWidth() int {
return a.Width
}
// GetHeight returns the desired height
func (a *Config) GetHeight() int {
return a.Height
}
// GetTitle returns the desired window title
func (a *Config) GetTitle() string {
return a.Title
}
// GetDefaultHTML returns the desired window title
func (a *Config) GetDefaultHTML() string {
return a.defaultHTML
}
// GetResizable returns true if the window should be resizable
func (a *Config) GetResizable() bool {
return a.Resizable
}
// GetDisableInspector returns true if the inspector should be disabled
func (a *Config) GetDisableInspector() bool {
return a.DisableInspector
}
// GetColour returns the colour
func (a *Config) GetColour() string {
return a.Colour
}
// GetCSS returns the user CSS
func (a *Config) GetCSS() string {
return a.CSS
}
// GetJS returns the user Javascript
func (a *Config) GetJS() string {
return a.JS
}
func (a *Config) merge(in *Config) error {
if in.CSS != "" {
a.CSS = in.CSS
}
if in.Title != "" {
a.Title = in.Title
}
// if in.HTML != "" {
// minified, err := htmlmin.Minify([]byte(in.HTML), &htmlmin.Options{
// MinifyScripts: true,
// })
// if err != nil {
// return err
// }
// inlineHTML := string(minified)
// inlineHTML = strings.Replace(inlineHTML, "'", "\\'", -1)
// inlineHTML = strings.Replace(inlineHTML, "\n", " ", -1)
// a.HTML = strings.TrimSpace(inlineHTML)
// // Deduce whether this is a full html page or a fragment
// // The document is determined to be a fragment if an HTML
// // tag exists and is located before the first div tag
// HTMLTagIndex := strings.Index(a.HTML, "<html")
// DivTagIndex := strings.Index(a.HTML, "<div")
// if HTMLTagIndex == -1 {
// a.isHTMLFragment = true
// } else {
// if DivTagIndex < HTMLTagIndex {
// a.isHTMLFragment = true
// }
// }
// }
if in.Colour != "" {
a.Colour = in.Colour
}
if in.JS != "" {
a.JS = in.JS
}
if in.Width != 0 {
a.Width = in.Width
}
if in.Height != 0 {
a.Height = in.Height
}
a.Resizable = in.Resizable
a.DisableInspector = in.DisableInspector
return nil
}
// Creates the default configuration
func newConfig(userConfig *Config) (*Config, error) {
result := &Config{
Width: 800,
Height: 600,
Resizable: true,
Title: "My Wails App",
Colour: "#FFF", // White by default
// HTML: mewn.String("./runtime/assets/default.html"),
}
if userConfig != nil {
err := result.merge(userConfig)
if err != nil {
return nil, err
}
}
return result, nil
}

View File

@@ -1,4 +1,4 @@
package wails package binding
import ( import (
"bytes" "bytes"
@@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"runtime" "runtime"
"github.com/wailsapp/wails/lib/logger"
) )
type boundFunction struct { type boundFunction struct {
@@ -14,7 +16,7 @@ type boundFunction struct {
functionType reflect.Type functionType reflect.Type
inputs []reflect.Type inputs []reflect.Type
returnTypes []reflect.Type returnTypes []reflect.Type
log *CustomLogger log *logger.CustomLogger
hasErrorReturnType bool hasErrorReturnType bool
} }
@@ -30,7 +32,7 @@ func newBoundFunction(object interface{}) (*boundFunction, error) {
fullName: name, fullName: name,
function: objectValue, function: objectValue,
functionType: objectType, functionType: objectType,
log: newCustomLogger(name), log: logger.NewCustomLogger(name),
} }
err := result.processParameters() err := result.processParameters()
@@ -55,7 +57,7 @@ func (b *boundFunction) processParameters() error {
b.inputs[index] = param b.inputs[index] = param
typ := param typ := param
index := index index := index
b.log.DebugFields("Input param", Fields{ b.log.DebugFields("Input param", logger.Fields{
"index": index, "index": index,
"name": name, "name": name,
"kind": kind, "kind": kind,

View File

@@ -1,27 +1,33 @@
package wails package binding
import "strings" import (
import "fmt" "fmt"
"strings"
type internalMethods struct{ "github.com/wailsapp/wails/lib/logger"
log *CustomLogger "github.com/wailsapp/wails/lib/messages"
browser *RuntimeBrowser "github.com/wailsapp/wails/runtime/go/runtime"
)
type internalMethods struct {
log *logger.CustomLogger
browser *runtime.Browser
} }
func newInternalMethods() *internalMethods { func newInternalMethods() *internalMethods {
return &internalMethods{ return &internalMethods{
log: newCustomLogger("InternalCall"), log: logger.NewCustomLogger("InternalCall"),
browser: newRuntimeBrowser(), browser: runtime.NewBrowser(),
} }
} }
func (i *internalMethods) processCall(callData *callData) (interface{}, error) { func (i *internalMethods) processCall(callData *messages.CallData) (interface{}, error) {
if !strings.HasPrefix(callData.BindingName, ".wails.") { if !strings.HasPrefix(callData.BindingName, ".wails.") {
return nil, fmt.Errorf("Invalid call signature '%s'", callData.BindingName) return nil, fmt.Errorf("Invalid call signature '%s'", callData.BindingName)
} }
// Strip prefix // Strip prefix
var splitCall = strings.Split(callData.BindingName,".")[2:] var splitCall = strings.Split(callData.BindingName, ".")[2:]
if len(splitCall) != 2 { if len(splitCall) != 2 {
return nil, fmt.Errorf("Invalid call signature '%s'", callData.BindingName) return nil, fmt.Errorf("Invalid call signature '%s'", callData.BindingName)
} }
@@ -37,14 +43,14 @@ func (i *internalMethods) processCall(callData *callData) (interface{}, error) {
func (i *internalMethods) processBrowserCommand(command string, data interface{}) (interface{}, error) { func (i *internalMethods) processBrowserCommand(command string, data interface{}) (interface{}, error) {
switch command { switch command {
case "OpenURL": case "OpenURL":
url := data.(string) url := data.(string)
// Strip string quotes. Credit: https://stackoverflow.com/a/44222648 // Strip string quotes. Credit: https://stackoverflow.com/a/44222648
if url[0] == '"' { if url[0] == '"' {
url = url[1:] url = url[1:]
} }
if i := len(url)-1; url[i] == '"' { if i := len(url) - 1; url[i] == '"' {
url = url[:i] url = url[:i]
} }
i.log.Debugf("Calling Browser.OpenURL with '%s'", url) i.log.Debugf("Calling Browser.OpenURL with '%s'", url)
return nil, i.browser.OpenURL(url) return nil, i.browser.OpenURL(url)
@@ -54,12 +60,12 @@ func (i *internalMethods) processBrowserCommand(command string, data interface{}
if filename[0] == '"' { if filename[0] == '"' {
filename = filename[1:] filename = filename[1:]
} }
if i := len(filename)-1; filename[i] == '"' { if i := len(filename) - 1; filename[i] == '"' {
filename = filename[:i] filename = filename[:i]
} }
i.log.Debugf("Calling Browser.OpenFile with '%s'", filename) i.log.Debugf("Calling Browser.OpenFile with '%s'", filename)
return nil, i.browser.OpenFile(filename) return nil, i.browser.OpenFile(filename)
default: default:
return nil, fmt.Errorf("Unknown Browser command '%s'", command) return nil, fmt.Errorf("Unknown Browser command '%s'", command)
} }
} }

View File

@@ -1,47 +1,46 @@
package wails package binding
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"unicode" "unicode"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/lib/messages"
"github.com/wailsapp/wails/lib/interfaces"
) )
/** // Manager handles method binding
type Manager struct {
binding:
Name() // Full name (package+name)
Call(params)
**/
type bindingManager struct {
methods map[string]*boundMethod methods map[string]*boundMethod
functions map[string]*boundFunction functions map[string]*boundFunction
internalMethods *internalMethods internalMethods *internalMethods
initMethods []*boundMethod initMethods []*boundMethod
log *CustomLogger log *logger.CustomLogger
renderer Renderer renderer interfaces.Renderer
runtime *Runtime // The runtime object to pass to bound structs runtime interfaces.Runtime // The runtime object to pass to bound structs
objectsToBind []interface{} objectsToBind []interface{}
bindPackageNames bool // Package name should be considered when binding bindPackageNames bool // Package name should be considered when binding
} }
func newBindingManager() *bindingManager { // NewManager creates a new Manager struct
result := &bindingManager{ func NewManager() interfaces.BindingManager {
result := &Manager{
methods: make(map[string]*boundMethod), methods: make(map[string]*boundMethod),
functions: make(map[string]*boundFunction), functions: make(map[string]*boundFunction),
log: newCustomLogger("Bind"), log: logger.NewCustomLogger("Bind"),
internalMethods: newInternalMethods(), internalMethods: newInternalMethods(),
} }
return result return result
} }
// Sets flag to indicate package names should be considered when binding // BindPackageNames sets a flag to indicate package names should be considered when binding
func (b *bindingManager) BindPackageNames() { func (b *Manager) BindPackageNames() {
b.bindPackageNames = true b.bindPackageNames = true
} }
func (b *bindingManager) start(renderer Renderer, runtime *Runtime) error { // Start the binding manager
func (b *Manager) Start(renderer interfaces.Renderer, runtime interfaces.Runtime) error {
b.log.Info("Starting") b.log.Info("Starting")
b.renderer = renderer b.renderer = renderer
b.runtime = runtime b.runtime = runtime
@@ -54,7 +53,7 @@ func (b *bindingManager) start(renderer Renderer, runtime *Runtime) error {
return err return err
} }
func (b *bindingManager) initialise() error { func (b *Manager) initialise() error {
var err error var err error
// var binding *boundMethod // var binding *boundMethod
@@ -92,7 +91,7 @@ func (b *bindingManager) initialise() error {
} }
// bind the given struct method // bind the given struct method
func (b *bindingManager) bindMethod(object interface{}) error { func (b *Manager) bindMethod(object interface{}) error {
objectType := reflect.TypeOf(object) objectType := reflect.TypeOf(object)
baseName := objectType.String() baseName := objectType.String()
@@ -142,7 +141,7 @@ func (b *bindingManager) bindMethod(object interface{}) error {
} }
// bind the given function object // bind the given function object
func (b *bindingManager) bindFunction(object interface{}) error { func (b *Manager) bindFunction(object interface{}) error {
newFunction, err := newBoundFunction(object) newFunction, err := newBoundFunction(object)
if err != nil { if err != nil {
@@ -159,18 +158,18 @@ func (b *bindingManager) bindFunction(object interface{}) error {
return nil return nil
} }
// Save the given object to be bound at start time // Bind saves the given object to be bound at start time
func (b *bindingManager) bind(object interface{}) { func (b *Manager) Bind(object interface{}) {
// Store binding // Store binding
b.objectsToBind = append(b.objectsToBind, object) b.objectsToBind = append(b.objectsToBind, object)
} }
func (b *bindingManager) processInternalCall(callData *callData) (interface{}, error) { func (b *Manager) processInternalCall(callData *messages.CallData) (interface{}, error) {
// Strip prefix // Strip prefix
return b.internalMethods.processCall(callData) return b.internalMethods.processCall(callData)
} }
func (b *bindingManager) processFunctionCall(callData *callData) (interface{}, error) { func (b *Manager) processFunctionCall(callData *messages.CallData) (interface{}, error) {
// Return values // Return values
var result []reflect.Value var result []reflect.Value
var err error var err error
@@ -199,7 +198,7 @@ func (b *bindingManager) processFunctionCall(callData *callData) (interface{}, e
return result[0].Interface(), nil return result[0].Interface(), nil
} }
func (b *bindingManager) processMethodCall(callData *callData) (interface{}, error) { func (b *Manager) processMethodCall(callData *messages.CallData) (interface{}, error) {
// Return values // Return values
var result []reflect.Value var result []reflect.Value
var err error var err error
@@ -233,8 +232,8 @@ func (b *bindingManager) processMethodCall(callData *callData) (interface{}, err
return nil, nil return nil, nil
} }
// process an incoming call request // ProcessCall processes the given call request
func (b *bindingManager) processCall(callData *callData) (result interface{}, err error) { func (b *Manager) ProcessCall(callData *messages.CallData) (result interface{}, err error) {
b.log.Debugf("Wanting to call %s", callData.BindingName) b.log.Debugf("Wanting to call %s", callData.BindingName)
// Determine if this is function call or method call by the number of // Determine if this is function call or method call by the number of
@@ -272,7 +271,7 @@ func (b *bindingManager) processCall(callData *callData) (result interface{}, er
// callWailsInitMethods calls all of the WailsInit methods that were // callWailsInitMethods calls all of the WailsInit methods that were
// registered with the runtime object // registered with the runtime object
func (b *bindingManager) callWailsInitMethods() error { func (b *Manager) callWailsInitMethods() error {
// Create reflect value for runtime object // Create reflect value for runtime object
runtimeValue := reflect.ValueOf(b.runtime) runtimeValue := reflect.ValueOf(b.runtime)
params := []reflect.Value{runtimeValue} params := []reflect.Value{runtimeValue}

View File

@@ -1,10 +1,12 @@
package wails package binding
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"github.com/wailsapp/wails/lib/logger"
) )
type boundMethod struct { type boundMethod struct {
@@ -13,7 +15,7 @@ type boundMethod struct {
method reflect.Value method reflect.Value
inputs []reflect.Type inputs []reflect.Type
returnTypes []reflect.Type returnTypes []reflect.Type
log *CustomLogger log *logger.CustomLogger
hasErrorReturnType bool // Indicates if there is an error return type hasErrorReturnType bool // Indicates if there is an error return type
isWailsInit bool isWailsInit bool
} }
@@ -27,7 +29,7 @@ func newBoundMethod(name string, fullName string, method reflect.Value, objectTy
} }
// Setup logger // Setup logger
result.log = newCustomLogger(result.fullName) result.log = logger.NewCustomLogger(result.fullName)
// Check if Parameters are valid // Check if Parameters are valid
err := result.processParameters() err := result.processParameters()
@@ -57,7 +59,7 @@ func (b *boundMethod) processParameters() error {
b.inputs[index] = param b.inputs[index] = param
typ := param typ := param
index := index index := index
b.log.DebugFields("Input param", Fields{ b.log.DebugFields("Input param", logger.Fields{
"index": index, "index": index,
"name": name, "name": name,
"kind": kind, "kind": kind,
@@ -166,10 +168,10 @@ func (b *boundMethod) setInputValue(index int, typ reflect.Type, val interface{}
reflect.Map, reflect.Map,
reflect.Ptr, reflect.Ptr,
reflect.Slice: reflect.Slice:
logger.Debug("Converting nil to type") b.log.Debug("Converting nil to type")
result = reflect.ValueOf(val).Convert(typ) result = reflect.ValueOf(val).Convert(typ)
default: default:
logger.Debug("Cannot convert nil to type, returning error") b.log.Debug("Cannot convert nil to type, returning error")
return reflect.Zero(typ), fmt.Errorf("Unable to use null value for parameter %d of method %s", index+1, b.fullName) return reflect.Zero(typ), fmt.Errorf("Unable to use null value for parameter %d of method %s", index+1, b.fullName)
} }
} else { } else {

View File

@@ -1,31 +1,34 @@
package wails package event
import ( import (
"fmt" "fmt"
"sync" "sync"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/lib/messages"
"github.com/wailsapp/wails/lib/interfaces"
) )
// eventManager handles and processes events // Manager handles and processes events
type eventManager struct { type Manager struct {
incomingEvents chan *eventData incomingEvents chan *messages.EventData
listeners map[string][]*eventListener listeners map[string][]*eventListener
exit bool exit bool
log *CustomLogger log *logger.CustomLogger
renderer Renderer // Messages will be dispatched to the frontend renderer interfaces.Renderer // Messages will be dispatched to the frontend
} }
// newEventManager creates a new event manager with a 100 event buffer // NewManager creates a new event manager with a 100 event buffer
func newEventManager() *eventManager { func NewManager() interfaces.EventManager {
return &eventManager{ return &Manager{
incomingEvents: make(chan *eventData, 100), incomingEvents: make(chan *messages.EventData, 100),
listeners: make(map[string][]*eventListener), listeners: make(map[string][]*eventListener),
exit: false, exit: false,
log: newCustomLogger("Events"), log: logger.NewCustomLogger("Events"),
} }
} }
// PushEvent places the given event on to the event queue // PushEvent places the given event on to the event queue
func (e *eventManager) PushEvent(eventData *eventData) { func (e *Manager) PushEvent(eventData *messages.EventData) {
e.incomingEvents <- eventData e.incomingEvents <- eventData
} }
@@ -40,7 +43,7 @@ type eventListener struct {
} }
// Creates a new event listener from the given callback function // Creates a new event listener from the given callback function
func (e *eventManager) addEventListener(eventName string, callback func(...interface{}), counter int) error { func (e *Manager) addEventListener(eventName string, callback func(...interface{}), counter int) error {
// Sanity check inputs // Sanity check inputs
if callback == nil { if callback == nil {
@@ -65,18 +68,19 @@ func (e *eventManager) addEventListener(eventName string, callback func(...inter
return nil return nil
} }
func (e *eventManager) On(eventName string, callback func(...interface{})) { // On adds a listener for the given event
func (e *Manager) On(eventName string, callback func(...interface{})) {
// Add a persistent eventListener (counter = 0) // Add a persistent eventListener (counter = 0)
e.addEventListener(eventName, callback, 0) e.addEventListener(eventName, callback, 0)
} }
// Emit broadcasts the given event to the subscribed listeners // Emit broadcasts the given event to the subscribed listeners
func (e *eventManager) Emit(eventName string, optionalData ...interface{}) { func (e *Manager) Emit(eventName string, optionalData ...interface{}) {
e.incomingEvents <- &eventData{Name: eventName, Data: optionalData} e.incomingEvents <- &messages.EventData{Name: eventName, Data: optionalData}
} }
// Starts the event manager's queue processing // Start the event manager's queue processing
func (e *eventManager) start(renderer Renderer) { func (e *Manager) Start(renderer interfaces.Renderer) {
e.log.Info("Starting") e.log.Info("Starting")
@@ -95,7 +99,7 @@ func (e *eventManager) start(renderer Renderer) {
// TODO: Listen for application exit // TODO: Listen for application exit
select { select {
case event := <-e.incomingEvents: case event := <-e.incomingEvents:
e.log.DebugFields("Got Event", Fields{ e.log.DebugFields("Got Event", logger.Fields{
"data": event.Data, "data": event.Data,
"name": event.Name, "name": event.Name,
}) })
@@ -143,6 +147,6 @@ func (e *eventManager) start(renderer Renderer) {
wg.Wait() wg.Wait()
} }
func (e *eventManager) stop() { func (e *Manager) stop() {
e.exit = true e.exit = true
} }

View File

@@ -0,0 +1,14 @@
package interfaces
// AppConfig is the application config interface
type AppConfig interface {
GetWidth() int
GetHeight() int
GetTitle() string
GetResizable() bool
GetDefaultHTML() string
GetDisableInspector() bool
GetColour() string
GetCSS() string
GetJS() string
}

View File

@@ -0,0 +1,10 @@
package interfaces
import "github.com/wailsapp/wails/lib/messages"
// BindingManager is the binding manager interface
type BindingManager interface {
Bind(object interface{})
Start(renderer Renderer, runtime Runtime) error
ProcessCall(callData *messages.CallData) (result interface{}, err error)
}

View File

@@ -0,0 +1,11 @@
package interfaces
import "github.com/wailsapp/wails/lib/messages"
// EventManager is the event manager interface
type EventManager interface {
PushEvent(*messages.EventData)
Emit(eventName string, optionalData ...interface{})
On(eventName string, callback func(...interface{}))
Start(Renderer)
}

View File

@@ -0,0 +1,8 @@
package interfaces
// IPCManager is the event manager interface
type IPCManager interface {
BindRenderer(Renderer)
Dispatch(message string)
Start(eventManager EventManager, bindingManager BindingManager)
}

View File

@@ -1,8 +1,11 @@
package wails package interfaces
import (
"github.com/wailsapp/wails/lib/messages"
)
// Renderer is an interface describing a Wails target to render the app to // Renderer is an interface describing a Wails target to render the app to
type Renderer interface { type Renderer interface {
Initialise(*AppConfig, *ipcManager, *eventManager) error Initialise(AppConfig, IPCManager, EventManager) error
Run() error Run() error
// Binding // Binding
@@ -10,7 +13,7 @@ type Renderer interface {
Callback(data string) error Callback(data string) error
// Events // Events
NotifyEvent(eventData *eventData) error NotifyEvent(eventData *messages.EventData) error
// Dialog Runtime // Dialog Runtime
SelectFile() string SelectFile() string

View File

@@ -0,0 +1,4 @@
package interfaces
// Runtime interface
type Runtime interface {}

View File

@@ -1,13 +1,10 @@
package wails package ipc
import ( import (
"fmt" "fmt"
)
type callData struct { "github.com/wailsapp/wails/lib/messages"
BindingName string `json:"bindingName"` )
Data string `json:"data,omitempty"`
}
func init() { func init() {
messageProcessors["call"] = processCallData messageProcessors["call"] = processCallData
@@ -15,7 +12,7 @@ func init() {
func processCallData(message *ipcMessage) (*ipcMessage, error) { func processCallData(message *ipcMessage) (*ipcMessage, error) {
var payload callData var payload messages.CallData
// Decode binding call data // Decode binding call data
payloadMap := message.Payload.(map[string]interface{}) payloadMap := message.Payload.(map[string]interface{})

View File

@@ -1,13 +1,10 @@
package wails package ipc
import ( import (
"encoding/json" "encoding/json"
)
type eventData struct { "github.com/wailsapp/wails/lib/messages"
Name string `json:"name"` )
Data interface{} `json:"data"`
}
// Register the message handler // Register the message handler
func init() { func init() {
@@ -19,7 +16,7 @@ func processEventData(message *ipcMessage) (*ipcMessage, error) {
// TODO: Is it worth double checking this is actually an event message, // TODO: Is it worth double checking this is actually an event message,
// even though that's done by the caller? // even though that's done by the caller?
var payload eventData var payload messages.EventData
// Decode event data // Decode event data
payloadMap := message.Payload.(map[string]interface{}) payloadMap := message.Payload.(map[string]interface{})

View File

@@ -1,9 +1,6 @@
package wails package ipc
type logData struct { import "github.com/wailsapp/wails/lib/messages"
Level string `json:"level"`
Message string `json:"string"`
}
// Register the message handler // Register the message handler
func init() { func init() {
@@ -13,7 +10,7 @@ func init() {
// This processes the given log message // This processes the given log message
func processLogData(message *ipcMessage) (*ipcMessage, error) { func processLogData(message *ipcMessage) (*ipcMessage, error) {
var payload logData var payload messages.LogData
// Decode event data // Decode event data
payloadMap := message.Payload.(map[string]interface{}) payloadMap := message.Payload.(map[string]interface{})

View File

@@ -1,35 +1,42 @@
package wails package ipc
import ( import (
"fmt" "fmt"
"github.com/wailsapp/wails/lib/interfaces"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/lib/messages"
) )
type ipcManager struct { // Manager manages the IPC subsystem
renderer Renderer // The renderer type Manager struct {
renderer interfaces.Renderer // The renderer
messageQueue chan *ipcMessage messageQueue chan *ipcMessage
// quitChannel chan struct{} // quitChannel chan struct{}
// signals chan os.Signal // signals chan os.Signal
log *CustomLogger log *logger.CustomLogger
eventManager *eventManager eventManager interfaces.EventManager
bindingManager *bindingManager bindingManager interfaces.BindingManager
} }
func newIPCManager() *ipcManager { // NewManager creates a new IPC Manager
result := &ipcManager{ func NewManager() interfaces.IPCManager {
result := &Manager{
messageQueue: make(chan *ipcMessage, 100), messageQueue: make(chan *ipcMessage, 100),
// quitChannel: make(chan struct{}), // quitChannel: make(chan struct{}),
// signals: make(chan os.Signal, 1), // signals: make(chan os.Signal, 1),
log: newCustomLogger("IPC"), log: logger.NewCustomLogger("IPC"),
} }
return result return result
} }
// Sets the renderer, returns the dispatch function // BindRenderer sets the renderer, returns the dispatch function
func (i *ipcManager) bindRenderer(renderer Renderer) { func (i *Manager) BindRenderer(renderer interfaces.Renderer) {
i.renderer = renderer i.renderer = renderer
} }
func (i *ipcManager) start(eventManager *eventManager, bindingManager *bindingManager) { // Start the IPC Manager
func (i *Manager) Start(eventManager interfaces.EventManager, bindingManager interfaces.BindingManager) {
// Store manager references // Store manager references
i.eventManager = eventManager i.eventManager = eventManager
@@ -42,36 +49,36 @@ func (i *ipcManager) start(eventManager *eventManager, bindingManager *bindingMa
for running { for running {
select { select {
case incomingMessage := <-i.messageQueue: case incomingMessage := <-i.messageQueue:
i.log.DebugFields("Processing message", Fields{ i.log.DebugFields("Processing message", logger.Fields{
"1D": &incomingMessage, "1D": &incomingMessage,
}) })
switch incomingMessage.Type { switch incomingMessage.Type {
case "call": case "call":
callData := incomingMessage.Payload.(*callData) callData := incomingMessage.Payload.(*messages.CallData)
i.log.DebugFields("Processing call", Fields{ i.log.DebugFields("Processing call", logger.Fields{
"1D": &incomingMessage, "1D": &incomingMessage,
"bindingName": callData.BindingName, "bindingName": callData.BindingName,
"data": callData.Data, "data": callData.Data,
}) })
go func() { go func() {
result, err := bindingManager.processCall(callData) result, err := bindingManager.ProcessCall(callData)
i.log.DebugFields("processed call", Fields{"result": result, "err": err}) i.log.DebugFields("processed call", logger.Fields{"result": result, "err": err})
if err != nil { if err != nil {
incomingMessage.ReturnError(err.Error()) incomingMessage.ReturnError(err.Error())
} else { } else {
incomingMessage.ReturnSuccess(result) incomingMessage.ReturnSuccess(result)
} }
i.log.DebugFields("Finished processing call", Fields{ i.log.DebugFields("Finished processing call", logger.Fields{
"1D": &incomingMessage, "1D": &incomingMessage,
}) })
}() }()
case "event": case "event":
// Extract event data // Extract event data
eventData := incomingMessage.Payload.(*eventData) eventData := incomingMessage.Payload.(*messages.EventData)
// Log // Log
i.log.DebugFields("Processing event", Fields{ i.log.DebugFields("Processing event", logger.Fields{
"name": eventData.Name, "name": eventData.Name,
"data": eventData.Data, "data": eventData.Data,
}) })
@@ -80,24 +87,24 @@ func (i *ipcManager) start(eventManager *eventManager, bindingManager *bindingMa
i.eventManager.PushEvent(eventData) i.eventManager.PushEvent(eventData)
// Log // Log
i.log.DebugFields("Finished processing event", Fields{ i.log.DebugFields("Finished processing event", logger.Fields{
"name": eventData.Name, "name": eventData.Name,
}) })
case "log": case "log":
logdata := incomingMessage.Payload.(*logData) logdata := incomingMessage.Payload.(*messages.LogData)
switch logdata.Level { switch logdata.Level {
case "info": case "info":
logger.Info(logdata.Message) i.log.Info(logdata.Message)
case "debug": case "debug":
logger.Debug(logdata.Message) i.log.Debug(logdata.Message)
case "warning": case "warning":
logger.Warning(logdata.Message) i.log.Warn(logdata.Message)
case "error": case "error":
logger.Error(logdata.Message) i.log.Error(logdata.Message)
case "fatal": case "fatal":
logger.Fatal(logdata.Message) i.log.Fatal(logdata.Message)
default: default:
i.log.ErrorFields("Invalid log level sent", Fields{ i.log.ErrorFields("Invalid log level sent", logger.Fields{
"level": logdata.Level, "level": logdata.Level,
"message": logdata.Message, "message": logdata.Message,
}) })
@@ -107,7 +114,7 @@ func (i *ipcManager) start(eventManager *eventManager, bindingManager *bindingMa
} }
// Log // Log
i.log.DebugFields("Finished processing message", Fields{ i.log.DebugFields("Finished processing message", logger.Fields{
"1D": &incomingMessage, "1D": &incomingMessage,
}) })
// case <-manager.quitChannel: // case <-manager.quitChannel:
@@ -125,7 +132,7 @@ func (i *ipcManager) start(eventManager *eventManager, bindingManager *bindingMa
// Dispatch receives JSON encoded messages from the renderer. // Dispatch receives JSON encoded messages from the renderer.
// It processes the message to ensure that it is valid and places // It processes the message to ensure that it is valid and places
// the processed message on the message queue // the processed message on the message queue
func (i *ipcManager) Dispatch(message string) { func (i *Manager) Dispatch(message string) {
// Create a new IPC Message // Create a new IPC Message
incomingMessage, err := newIPCMessage(message, i.SendResponse) incomingMessage, err := newIPCMessage(message, i.SendResponse)
@@ -148,7 +155,7 @@ func (i *ipcManager) Dispatch(message string) {
} }
// SendResponse sends the given response back to the frontend // SendResponse sends the given response back to the frontend
func (i *ipcManager) SendResponse(response *ipcResponse) error { func (i *Manager) SendResponse(response *ipcResponse) error {
// Serialise the Message // Serialise the Message
data, err := response.Serialise() data, err := response.Serialise()

View File

@@ -1,4 +1,4 @@
package wails package ipc
import ( import (
"encoding/json" "encoding/json"

View File

@@ -1,4 +1,4 @@
package wails package ipc
import ( import (
"encoding/hex" "encoding/hex"

View File

@@ -1,4 +1,4 @@
package wails package logger
// CustomLogger is a wrapper object to logrus // CustomLogger is a wrapper object to logrus
type CustomLogger struct { type CustomLogger struct {
@@ -6,7 +6,8 @@ type CustomLogger struct {
errorOnly bool errorOnly bool
} }
func newCustomLogger(prefix string) *CustomLogger { // NewCustomLogger creates a new custom logger with the given prefix
func NewCustomLogger(prefix string) *CustomLogger {
return &CustomLogger{ return &CustomLogger{
prefix: "[" + prefix + "] ", prefix: "[" + prefix + "] ",
} }

View File

@@ -1,14 +1,14 @@
package wails package logger
import ( import (
"os" "os"
"strings" "strings"
log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// Global logger reference // Global logger reference
var logger = log.New() var logger = logrus.New()
// Fields is used by the customLogger object to output // Fields is used by the customLogger object to output
// fields along with a message // fields along with a message
@@ -17,26 +17,26 @@ type Fields map[string]interface{}
// Default options for the global logger // Default options for the global logger
func init() { func init() {
logger.SetOutput(os.Stdout) logger.SetOutput(os.Stdout)
logger.SetLevel(log.DebugLevel) logger.SetLevel(logrus.DebugLevel)
} }
// Sets the log level to the given level // SetLogLevel sets the log level to the given level
func setLogLevel(level string) { func SetLogLevel(level string) {
switch strings.ToLower(level) { switch strings.ToLower(level) {
case "info": case "info":
logger.SetLevel(log.InfoLevel) logger.SetLevel(logrus.InfoLevel)
case "debug": case "debug":
logger.SetLevel(log.DebugLevel) logger.SetLevel(logrus.DebugLevel)
case "warn": case "warn":
logger.SetLevel(log.WarnLevel) logger.SetLevel(logrus.WarnLevel)
case "error": case "error":
logger.SetLevel(log.ErrorLevel) logger.SetLevel(logrus.ErrorLevel)
case "fatal": case "fatal":
logger.SetLevel(log.FatalLevel) logger.SetLevel(logrus.FatalLevel)
case "panic": case "panic":
logger.SetLevel(log.PanicLevel) logger.SetLevel(logrus.PanicLevel)
default: default:
logger.SetLevel(log.DebugLevel) logger.SetLevel(logrus.DebugLevel)
logger.Warnf("Log level '%s' not recognised. Setting to Debug.", level) logger.Warnf("Log level '%s' not recognised. Setting to Debug.", level)
} }
} }

7
lib/messages/calldata.go Normal file
View File

@@ -0,0 +1,7 @@
package messages
// CallData represents a call to a Go function/method
type CallData struct {
BindingName string `json:"bindingName"`
Data string `json:"data,omitempty"`
}

View File

@@ -0,0 +1,7 @@
package messages
// EventData represents an event sent from the frontend
type EventData struct {
Name string `json:"name"`
Data interface{} `json:"data"`
}

7
lib/messages/logdata.go Normal file
View File

@@ -0,0 +1,7 @@
package messages
// LogData represents a call to log from the frontend
type LogData struct {
Level string `json:"level"`
Message string `json:"string"`
}

View File

@@ -1,4 +1,4 @@
package wails package renderer
import ( import (
"encoding/json" "encoding/json"
@@ -10,6 +10,9 @@ import (
"github.com/dchest/htmlmin" "github.com/dchest/htmlmin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/leaanthony/mewn" "github.com/leaanthony/mewn"
"github.com/wailsapp/wails/lib/interfaces"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/lib/messages"
) )
type messageType int type messageType int
@@ -32,10 +35,10 @@ func (m messageType) toString() string {
// and renders the files over a websocket // and renders the files over a websocket
type Headless struct { type Headless struct {
// Common // Common
log *CustomLogger log *logger.CustomLogger
ipcManager *ipcManager ipcManager interfaces.IPCManager
appConfig *AppConfig appConfig interfaces.AppConfig
eventManager *eventManager eventManager interfaces.EventManager
bindingCache []string bindingCache []string
// Headless specific // Headless specific
@@ -48,12 +51,12 @@ type Headless struct {
} }
// Initialise the Headless Renderer // Initialise the Headless Renderer
func (h *Headless) Initialise(appConfig *AppConfig, ipcManager *ipcManager, eventManager *eventManager) error { func (h *Headless) Initialise(appConfig interfaces.AppConfig, ipcManager interfaces.IPCManager, eventManager interfaces.EventManager) error {
h.ipcManager = ipcManager h.ipcManager = ipcManager
h.appConfig = appConfig h.appConfig = appConfig
h.eventManager = eventManager h.eventManager = eventManager
ipcManager.bindRenderer(h) ipcManager.BindRenderer(h)
h.log = newCustomLogger("Bridge") h.log = logger.NewCustomLogger("Bridge")
return nil return nil
} }
@@ -117,7 +120,7 @@ func (h *Headless) start(conn *websocket.Conn) {
// set external.invoke // set external.invoke
h.log.Infof("Connected to frontend.") h.log.Infof("Connected to frontend.")
wailsRuntime := mewn.String("./wailsruntimeassets/default/wails.min.js") wailsRuntime := mewn.String("../../runtime/js/dist/wails.js")
h.evalJS(wailsRuntime, wailsRuntimeMessage) h.evalJS(wailsRuntime, wailsRuntimeMessage)
// Inject bindings // Inject bindings
@@ -192,13 +195,13 @@ func (h *Headless) Callback(data string) error {
} }
// NotifyEvent notifies the frontend of an event // NotifyEvent notifies the frontend of an event
func (h *Headless) NotifyEvent(event *eventData) error { func (h *Headless) NotifyEvent(event *messages.EventData) error {
// Look out! Nils about! // Look out! Nils about!
var err error var err error
if event == nil { if event == nil {
err = fmt.Errorf("Sent nil event to renderer.webViewRenderer") err = fmt.Errorf("Sent nil event to renderer.webViewRenderer")
logger.Error(err) h.log.Error(err.Error())
return err return err
} }
@@ -222,7 +225,7 @@ func (h *Headless) NotifyEvent(event *eventData) error {
// SetColour is unsupported for Headless but required // SetColour is unsupported for Headless but required
// for the Renderer interface // for the Renderer interface
func (h *Headless) SetColour(colour string) error { func (h *Headless) SetColour(colour string) error {
h.log.WarnFields("SetColour ignored for headless more", Fields{"col": colour}) h.log.WarnFields("SetColour ignored for headless more", logger.Fields{"col": colour})
return nil return nil
} }
@@ -241,7 +244,7 @@ func (h *Headless) UnFullscreen() {
// SetTitle is currently unsupported for Headless but required // SetTitle is currently unsupported for Headless but required
// for the Renderer interface // for the Renderer interface
func (h *Headless) SetTitle(title string) { func (h *Headless) SetTitle(title string) {
h.log.WarnFields("SetTitle() unsupported in bridge mode", Fields{"title": title}) h.log.WarnFields("SetTitle() unsupported in bridge mode", logger.Fields{"title": title})
} }
// Close is unsupported for Headless but required // Close is unsupported for Headless but required

File diff suppressed because one or more lines are too long

View File

@@ -1,52 +1,61 @@
package wails package renderer
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"strings"
"sync" "sync"
"time" "time"
"github.com/go-playground/colors" "github.com/go-playground/colors"
"github.com/leaanthony/mewn" "github.com/leaanthony/mewn"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/lib/messages"
"github.com/wailsapp/wails/lib/interfaces"
"github.com/wailsapp/webview" "github.com/wailsapp/webview"
) )
// Window defines the main application window // WebView defines the main webview application window
// Default values in [] // Default values in []
type webViewRenderer struct { type WebView struct {
window webview.WebView // The webview object window webview.WebView // The webview object
ipc *ipcManager ipc interfaces.IPCManager
log *CustomLogger log *logger.CustomLogger
config *AppConfig config interfaces.AppConfig
eventManager *eventManager eventManager interfaces.EventManager
bindingCache []string bindingCache []string
} }
// NewWebView returns a new WebView struct
func NewWebView() *WebView {
return &WebView{};
}
// Initialise sets up the WebView // Initialise sets up the WebView
func (w *webViewRenderer) Initialise(config *AppConfig, ipc *ipcManager, eventManager *eventManager) error { func (w *WebView) Initialise(config interfaces.AppConfig, ipc interfaces.IPCManager, eventManager interfaces.EventManager) error {
// Store reference to eventManager // Store reference to eventManager
w.eventManager = eventManager w.eventManager = eventManager
// Set up logger // Set up logger
w.log = newCustomLogger("WebView") w.log = logger.NewCustomLogger("WebView")
// Set up the dispatcher function // Set up the dispatcher function
w.ipc = ipc w.ipc = ipc
ipc.bindRenderer(w) ipc.BindRenderer(w)
// Save the config // Save the config
w.config = config w.config = config
// Create the WebView instance // Create the WebView instance
w.window = webview.NewWebview(webview.Settings{ w.window = webview.NewWebview(webview.Settings{
Width: config.Width, Width: config.GetWidth(),
Height: config.Height, Height: config.GetHeight(),
Title: config.Title, Title: config.GetTitle(),
Resizable: config.Resizable, Resizable: config.GetResizable(),
URL: config.defaultHTML, URL: config.GetDefaultHTML(),
Debug: !config.DisableInspector, Debug: !config.GetDisableInspector(),
ExternalInvokeCallback: func(_ webview.WebView, message string) { ExternalInvokeCallback: func(_ webview.WebView, message string) {
w.ipc.Dispatch(message) w.ipc.Dispatch(message)
}, },
@@ -55,7 +64,7 @@ func (w *webViewRenderer) Initialise(config *AppConfig, ipc *ipcManager, eventMa
// SignalManager.OnExit(w.Exit) // SignalManager.OnExit(w.Exit)
// Set colour // Set colour
err := w.SetColour(config.Colour) err := w.SetColour(config.GetColour())
if err != nil { if err != nil {
return err return err
} }
@@ -64,7 +73,8 @@ func (w *webViewRenderer) Initialise(config *AppConfig, ipc *ipcManager, eventMa
return nil return nil
} }
func (w *webViewRenderer) SetColour(colour string) error { // SetColour sets the window colour
func (w *WebView) SetColour(colour string) error {
color, err := colors.Parse(colour) color, err := colors.Parse(colour)
if err != nil { if err != nil {
return err return err
@@ -80,12 +90,12 @@ func (w *webViewRenderer) SetColour(colour string) error {
// evalJS evaluates the given js in the WebView // evalJS evaluates the given js in the WebView
// I should rename this to evilJS lol // I should rename this to evilJS lol
func (w *webViewRenderer) evalJS(js string) error { func (w *WebView) evalJS(js string) error {
outputJS := fmt.Sprintf("%.45s", js) outputJS := fmt.Sprintf("%.45s", js)
if len(js) > 45 { if len(js) > 45 {
outputJS += "..." outputJS += "..."
} }
w.log.DebugFields("Eval", Fields{"js": outputJS}) w.log.DebugFields("Eval", logger.Fields{"js": outputJS})
// //
w.window.Dispatch(func() { w.window.Dispatch(func() {
w.window.Eval(js) w.window.Eval(js)
@@ -93,12 +103,21 @@ func (w *webViewRenderer) evalJS(js string) error {
return nil return nil
} }
// Escape the Javascripts!
func escapeJS(js string) (string, error) {
result := strings.Replace(js, "\\", "\\\\", -1)
result = strings.Replace(result, "'", "\\'", -1)
result = strings.Replace(result, "\n", "\\n", -1)
return result, nil
}
// evalJSSync evaluates the given js in the WebView synchronously // evalJSSync evaluates the given js in the WebView synchronously
// Do not call this from the main thread or you'll nuke your app because // Do not call this from the main thread or you'll nuke your app because
// you won't get the callback. // you won't get the callback.
func (w *webViewRenderer) evalJSSync(js string) error { func (w *WebView) evalJSSync(js string) error {
minified, err := escapeJS(js) minified, err := escapeJS(js)
if err != nil { if err != nil {
return err return err
} }
@@ -107,7 +126,7 @@ func (w *webViewRenderer) evalJSSync(js string) error {
if len(js) > 45 { if len(js) > 45 {
outputJS += "..." outputJS += "..."
} }
w.log.DebugFields("EvalSync", Fields{"js": outputJS}) w.log.DebugFields("EvalSync", logger.Fields{"js": outputJS})
ID := fmt.Sprintf("syncjs:%d:%d", time.Now().Unix(), rand.Intn(9999)) ID := fmt.Sprintf("syncjs:%d:%d", time.Now().Unix(), rand.Intn(9999))
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -137,24 +156,24 @@ func (w *webViewRenderer) evalJSSync(js string) error {
} }
// injectCSS adds the given CSS to the WebView // injectCSS adds the given CSS to the WebView
func (w *webViewRenderer) injectCSS(css string) { func (w *WebView) injectCSS(css string) {
w.window.Dispatch(func() { w.window.Dispatch(func() {
w.window.InjectCSS(css) w.window.InjectCSS(css)
}) })
} }
// Quit the window // Exit closes the window
func (w *webViewRenderer) Exit() { func (w *WebView) Exit() {
w.window.Exit() w.window.Exit()
} }
// Run the window main loop // Run the window main loop
func (w *webViewRenderer) Run() error { func (w *WebView) Run() error {
w.log.Info("Run()") w.log.Info("Run()")
// Runtime assets // Runtime assets
wailsRuntime := mewn.String("./wailsruntimeassets/default/wails.min.js") wailsRuntime := mewn.String("../../runtime/js/dist/wails.js")
w.evalJS(wailsRuntime) w.evalJS(wailsRuntime)
// Ping the wait channel when the wails runtime is loaded // Ping the wait channel when the wails runtime is loaded
@@ -168,38 +187,30 @@ func (w *webViewRenderer) Run() error {
w.evalJSSync(binding) w.evalJSSync(binding)
} }
// // Inject Framework
// if w.frameworkJS != "" {
// w.evalJSSync(w.frameworkJS)
// }
// if w.frameworkCSS != "" {
// w.injectCSS(w.frameworkCSS)
// }
// Inject user CSS // Inject user CSS
if w.config.CSS != "" { if w.config.GetCSS() != "" {
outputCSS := fmt.Sprintf("%.45s", w.config.CSS) outputCSS := fmt.Sprintf("%.45s", w.config.GetCSS())
if len(outputCSS) > 45 { if len(outputCSS) > 45 {
outputCSS += "..." outputCSS += "..."
} }
w.log.DebugFields("Inject User CSS", Fields{"css": outputCSS}) w.log.DebugFields("Inject User CSS", logger.Fields{"css": outputCSS})
w.injectCSS(w.config.CSS) w.injectCSS(w.config.GetCSS())
} else { } else {
// Use default wails css // Use default wails css
w.log.Debug("Injecting Default Wails CSS") w.log.Debug("Injecting Default Wails CSS")
defaultCSS := mewn.String("./wailsruntimeassets/default/wails.css") defaultCSS := mewn.String("../../runtime/assets/wails.css")
w.injectCSS(defaultCSS) w.injectCSS(defaultCSS)
} }
// Inject user JS // Inject user JS
if w.config.JS != "" { if w.config.GetJS() != "" {
outputJS := fmt.Sprintf("%.45s", w.config.JS) outputJS := fmt.Sprintf("%.45s", w.config.GetJS())
if len(outputJS) > 45 { if len(outputJS) > 45 {
outputJS += "..." outputJS += "..."
} }
w.log.DebugFields("Inject User JS", Fields{"js": outputJS}) w.log.DebugFields("Inject User JS", logger.Fields{"js": outputJS})
w.evalJSSync(w.config.JS) w.evalJSSync(w.config.GetJS())
} }
// Emit that everything is loaded and ready // Emit that everything is loaded and ready
@@ -213,14 +224,15 @@ func (w *webViewRenderer) Run() error {
return nil return nil
} }
// Binds the given method name with the front end // NewBinding registers a new binding with the frontend
func (w *webViewRenderer) NewBinding(methodName string) error { func (w *WebView) NewBinding(methodName string) error {
objectCode := fmt.Sprintf("window.wails._.newBinding('%s');", methodName) objectCode := fmt.Sprintf("window.wails._.newBinding('%s');", methodName)
w.bindingCache = append(w.bindingCache, objectCode) w.bindingCache = append(w.bindingCache, objectCode)
return nil return nil
} }
func (w *webViewRenderer) SelectFile() string { // SelectFile opens a dialog that allows the user to select a file
func (w *WebView) SelectFile() string {
var result string var result string
// We need to run this on the main thread, however Dispatch is // We need to run this on the main thread, however Dispatch is
@@ -238,7 +250,8 @@ func (w *webViewRenderer) SelectFile() string {
return result return result
} }
func (w *webViewRenderer) SelectDirectory() string { // SelectDirectory opens a dialog that allows the user to select a directory
func (w *WebView) SelectDirectory() string {
var result string var result string
// We need to run this on the main thread, however Dispatch is // We need to run this on the main thread, however Dispatch is
// non-blocking so we launch this in a goroutine and wait for // non-blocking so we launch this in a goroutine and wait for
@@ -255,7 +268,8 @@ func (w *webViewRenderer) SelectDirectory() string {
return result return result
} }
func (w *webViewRenderer) SelectSaveFile() string { // SelectSaveFile opens a dialog that allows the user to select a file to save
func (w *WebView) SelectSaveFile() string {
var result string var result string
// We need to run this on the main thread, however Dispatch is // We need to run this on the main thread, however Dispatch is
// non-blocking so we launch this in a goroutine and wait for // non-blocking so we launch this in a goroutine and wait for
@@ -273,18 +287,19 @@ func (w *webViewRenderer) SelectSaveFile() string {
} }
// Callback sends a callback to the frontend // Callback sends a callback to the frontend
func (w *webViewRenderer) Callback(data string) error { func (w *WebView) Callback(data string) error {
callbackCMD := fmt.Sprintf("window.wails._.callback('%s');", data) callbackCMD := fmt.Sprintf("window.wails._.callback('%s');", data)
return w.evalJS(callbackCMD) return w.evalJS(callbackCMD)
} }
func (w *webViewRenderer) NotifyEvent(event *eventData) error { // NotifyEvent notifies the frontend about a backend runtime event
func (w *WebView) NotifyEvent(event *messages.EventData) error {
// Look out! Nils about! // Look out! Nils about!
var err error var err error
if event == nil { if event == nil {
err = fmt.Errorf("Sent nil event to renderer.webViewRenderer") err = fmt.Errorf("Sent nil event to renderer.WebView")
logger.Error(err) w.log.Error(err.Error())
return err return err
} }
@@ -305,9 +320,9 @@ func (w *webViewRenderer) NotifyEvent(event *eventData) error {
return w.evalJS(message) return w.evalJS(message)
} }
// Window // Fullscreen makes the main window go fullscreen
func (w *webViewRenderer) Fullscreen() { func (w *WebView) Fullscreen() {
if w.config.Resizable == false { if w.config.GetResizable() == false {
w.log.Warn("Cannot call Fullscreen() - App.Resizable = false") w.log.Warn("Cannot call Fullscreen() - App.Resizable = false")
return return
} }
@@ -316,8 +331,9 @@ func (w *webViewRenderer) Fullscreen() {
}) })
} }
func (w *webViewRenderer) UnFullscreen() { // UnFullscreen returns the window to the position prior to a fullscreen call
if w.config.Resizable == false { func (w *WebView) UnFullscreen() {
if w.config.GetResizable() == false {
w.log.Warn("Cannot call UnFullscreen() - App.Resizable = false") w.log.Warn("Cannot call UnFullscreen() - App.Resizable = false")
return return
} }
@@ -326,13 +342,15 @@ func (w *webViewRenderer) UnFullscreen() {
}) })
} }
func (w *webViewRenderer) SetTitle(title string) { // SetTitle sets the window title
func (w *WebView) SetTitle(title string) {
w.window.Dispatch(func() { w.window.Dispatch(func() {
w.window.SetTitle(title) w.window.SetTitle(title)
}) })
} }
func (w *webViewRenderer) Close() { // Close closes the window
func (w *WebView) Close() {
w.window.Dispatch(func() { w.window.Dispatch(func() {
w.window.Terminate() w.window.Terminate()
}) })

View File

@@ -1,22 +0,0 @@
package wails
// Runtime is the Wails Runtime Interface, given to a user who has defined the WailsInit method
type Runtime struct {
Events *RuntimeEvents
Log *RuntimeLog
Dialog *RuntimeDialog
Window *RuntimeWindow
Browser *RuntimeBrowser
FileSystem *RuntimeFileSystem
}
func newRuntime(eventManager *eventManager, renderer Renderer) *Runtime {
return &Runtime{
Events: newRuntimeEvents(eventManager),
Log: newRuntimeLog(),
Dialog: newRuntimeDialog(renderer),
Window: newRuntimeWindow(renderer),
Browser: newRuntimeBrowser(),
FileSystem: newRuntimeFileSystem(),
}
}

View File

@@ -0,0 +1,21 @@
package runtime
import "github.com/pkg/browser"
// Browser exposes browser methods to the runtime
type Browser struct{}
// NewBrowser creates a new runtime Browser struct
func NewBrowser() *Browser {
return &Browser{}
}
// OpenURL opens the given url in the system's default browser
func (r *Browser) OpenURL(url string) error {
return browser.OpenURL(url)
}
// OpenFile opens the given file in the system's default browser
func (r *Browser) OpenFile(filePath string) error {
return browser.OpenFile(filePath)
}

View File

@@ -0,0 +1,30 @@
package runtime
import "github.com/wailsapp/wails/lib/interfaces"
// Dialog exposes an interface to native dialogs
type Dialog struct {
renderer interfaces.Renderer
}
// newDialog creates a new Dialog struct
func newDialog(renderer interfaces.Renderer) *Dialog {
return &Dialog{
renderer: renderer,
}
}
// SelectFile prompts the user to select a file
func (r *Dialog) SelectFile() string {
return r.renderer.SelectFile()
}
// SelectDirectory prompts the user to select a directory
func (r *Dialog) SelectDirectory() string {
return r.renderer.SelectDirectory()
}
// SelectSaveFile prompts the user to select a file for saving
func (r *Dialog) SelectSaveFile() string {
return r.renderer.SelectSaveFile()
}

View File

@@ -0,0 +1,24 @@
package runtime
import "github.com/wailsapp/wails/lib/interfaces"
// Events exposes the events interface
type Events struct {
eventManager interfaces.EventManager
}
func newEvents(eventManager interfaces.EventManager) *Events {
return &Events{
eventManager: eventManager,
}
}
// On pass through
func (r *Events) On(eventName string, callback func(optionalData ...interface{})) {
r.eventManager.On(eventName, callback)
}
// Emit pass through
func (r *Events) Emit(eventName string, optionalData ...interface{}) {
r.eventManager.Emit(eventName, optionalData...)
}

View File

@@ -0,0 +1,16 @@
package runtime
import homedir "github.com/mitchellh/go-homedir"
// FileSystem exposes file system utilities to the runtime
type FileSystem struct {}
// Creates a new FileSystem struct
func newFileSystem() *FileSystem {
return &FileSystem{}
}
// HomeDir returns the user's home directory
func (r *FileSystem) HomeDir() (string, error) {
return homedir.Dir()
}

16
runtime/go/runtime/log.go Normal file
View File

@@ -0,0 +1,16 @@
package runtime
import "github.com/wailsapp/wails/lib/logger"
// Log exposes the logging interface to the runtime
type Log struct{}
// newLog creates a new Log struct
func newLog() *Log {
return &Log{}
}
// New creates a new logger
func (r *Log) New(prefix string) *logger.CustomLogger {
return logger.NewCustomLogger(prefix)
}

View File

@@ -0,0 +1,25 @@
package runtime
import "github.com/wailsapp/wails/lib/interfaces"
// Runtime is the Wails Runtime Interface, given to a user who has defined the WailsInit method
type Runtime struct {
Events *Events
Log *Log
Dialog *Dialog
Window *Window
Browser *Browser
FileSystem *FileSystem
}
// NewRuntime creates a new Runtime struct
func NewRuntime(eventManager interfaces.EventManager, renderer interfaces.Renderer) *Runtime {
return &Runtime{
Events: newEvents(eventManager),
Log: newLog(),
Dialog: newDialog(renderer),
Window: newWindow(renderer),
Browser: NewBrowser(),
FileSystem: newFileSystem(),
}
}

View File

@@ -1,37 +1,39 @@
package wails package runtime
// RuntimeWindow exposes an interface for manipulating the window import "github.com/wailsapp/wails/lib/interfaces"
type RuntimeWindow struct {
renderer Renderer // Window exposes an interface for manipulating the window
type Window struct {
renderer interfaces.Renderer
} }
func newRuntimeWindow(renderer Renderer) *RuntimeWindow { func newWindow(renderer interfaces.Renderer) *Window {
return &RuntimeWindow{ return &Window{
renderer: renderer, renderer: renderer,
} }
} }
// SetColour sets the the window colour // SetColour sets the the window colour
func (r *RuntimeWindow) SetColour(colour string) error { func (r *Window) SetColour(colour string) error {
return r.renderer.SetColour(colour) return r.renderer.SetColour(colour)
} }
// Fullscreen makes the window fullscreen // Fullscreen makes the window fullscreen
func (r *RuntimeWindow) Fullscreen() { func (r *Window) Fullscreen() {
r.renderer.Fullscreen() r.renderer.Fullscreen()
} }
// UnFullscreen attempts to restore the window to the size/position before fullscreen // UnFullscreen attempts to restore the window to the size/position before fullscreen
func (r *RuntimeWindow) UnFullscreen() { func (r *Window) UnFullscreen() {
r.renderer.UnFullscreen() r.renderer.UnFullscreen()
} }
// SetTitle sets the the window title // SetTitle sets the the window title
func (r *RuntimeWindow) SetTitle(title string) { func (r *Window) SetTitle(title string) {
r.renderer.SetTitle(title) r.renderer.SetTitle(title)
} }
// Close shuts down the window and therefore the app // Close shuts down the window and therefore the app
func (r *RuntimeWindow) Close() { func (r *Window) Close() {
r.renderer.Close() r.renderer.Close()
} }

280
runtime/js/.eslintrc.js Normal file
View File

@@ -0,0 +1,280 @@
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2015,
"sourceType": "module"
},
"rules": {
"accessor-pairs": "error",
"array-bracket-newline": "error",
"array-bracket-spacing": [
"error",
"never"
],
"array-callback-return": "error",
"array-element-newline": "off",
"arrow-body-style": "error",
"arrow-parens": "error",
"arrow-spacing": "error",
"block-scoped-var": "off",
"block-spacing": "error",
"brace-style": [
"error",
"1tbs"
],
"callback-return": "error",
"camelcase": "error",
"capitalized-comments": "off",
"class-methods-use-this": "error",
"comma-dangle": "off",
"comma-spacing": [
"error",
{
"after": true,
"before": false
}
],
"comma-style": [
"error",
"last"
],
"complexity": "error",
"computed-property-spacing": [
"error",
"never"
],
"consistent-return": "off",
"consistent-this": "error",
"curly": "error",
"default-case": "error",
"dot-location": "error",
"dot-notation": "error",
"eol-last": "off",
"eqeqeq": "off",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": [
"error",
"never"
],
"func-style": [
"error",
"declaration"
],
"function-paren-newline": "error",
"generator-star-spacing": "error",
"global-require": "error",
"guard-for-in": "off",
"handle-callback-err": "error",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"implicit-arrow-linebreak": "error",
"indent": "off",
"indent-legacy": "off",
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "error",
"keyword-spacing": [
"error",
{
"after": true,
"before": true
}
],
"line-comment-position": "error",
"linebreak-style": [
"error",
"unix"
],
"lines-around-comment": "error",
"lines-around-directive": "error",
"lines-between-class-members": "error",
"max-classes-per-file": "error",
"max-depth": "error",
"max-len": "off",
"max-lines": "error",
"max-lines-per-function": "error",
"max-nested-callbacks": "error",
"max-params": "error",
"max-statements": "off",
"max-statements-per-line": "error",
"multiline-comment-style": "off",
"multiline-ternary": "error",
"new-parens": "error",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-await-in-loop": "error",
"no-bitwise": "error",
"no-buffer-constructor": "error",
"no-caller": "error",
"no-catch-shadow": "error",
"no-confusing-arrow": "error",
"no-continue": "error",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-empty-function": "error",
"no-eq-null": "off",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "error",
"no-floating-decimal": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "error",
"no-inner-declarations": [
"error",
"functions"
],
"no-invalid-this": "error",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-operators": "error",
"no-mixed-requires": "error",
"no-multi-assign": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-multiple-empty-lines": "error",
"no-native-reassign": "error",
"no-negated-condition": "error",
"no-negated-in-lhs": "error",
"no-nested-ternary": "error",
"no-new": "off",
"no-new-func": "off",
"no-new-object": "error",
"no-new-require": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-path-concat": "error",
"no-plusplus": "error",
"no-process-env": "error",
"no-process-exit": "error",
"no-proto": "error",
"no-prototype-builtins": "error",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-modules": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-return-await": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-sync": "error",
"no-tabs": "error",
"no-template-curly-in-string": "error",
"no-ternary": "error",
"no-throw-literal": "error",
"no-undef-init": "error",
"no-undefined": "off",
"no-underscore-dangle": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-use-before-define": "error",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "off",
"no-void": "error",
"no-warning-comments": "error",
"no-whitespace-before-property": "error",
"no-with": "error",
"nonblock-statement-body-position": "error",
"object-curly-newline": "error",
"object-curly-spacing": [
"error",
"always"
],
"object-property-newline": "error",
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "error",
"operator-assignment": "error",
"operator-linebreak": "error",
"padded-blocks": "off",
"padding-line-between-statements": "error",
"prefer-arrow-callback": "off",
"prefer-const": "error",
"prefer-destructuring": "off",
"prefer-numeric-literals": "error",
"prefer-object-spread": "error",
"prefer-promise-reject-errors": "error",
"prefer-reflect": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-template": "off",
"quote-props": "off",
"quotes": [
"error",
"single"
],
"radix": "error",
"require-await": "error",
"require-jsdoc": "off",
"rest-spread-spacing": "error",
"semi": "off",
"semi-spacing": "error",
"semi-style": [
"error",
"last"
],
"sort-imports": "off",
"sort-keys": "off",
"sort-vars": "error",
"space-before-blocks": "error",
"space-before-function-paren": "off",
"space-in-parens": [
"error",
"never"
],
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": [
"error",
"always"
],
"strict": "error",
"switch-colon-spacing": "error",
"symbol-description": "error",
"template-curly-spacing": "error",
"template-tag-spacing": "error",
"unicode-bom": [
"error",
"never"
],
"valid-jsdoc": "error",
"vars-on-top": "off",
"wrap-iife": "off",
"wrap-regex": "error",
"yield-star-spacing": "error",
"yoda": [
"error",
"never"
]
}
};

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
module.exports = function (api) {
api.cache(true);
const presets = [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": {
"version": 3,
"proposals": true
}
}
]
];
return {
presets,
};
}

1
runtime/js/dist/wails.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(n){var e={};function t(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,r){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:r})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(r,o,function(e){return n[e]}.bind(null,o));return r},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){"use strict";t.r(e);var r={};t.r(r),t.d(r,"Debug",function(){return c}),t.d(r,"Info",function(){return u}),t.d(r,"Warning",function(){return l}),t.d(r,"Error",function(){return f}),t.d(r,"Fatal",function(){return d});var o=window&&window.external&&window.external.invoke?window.external.invoke:console.log;function a(n,e,t){o(JSON.stringify({type:n,callbackID:t,payload:e}))}function i(n,e){a("log",{level:n,message:e})}function c(n){i("debug",n)}function u(n){i("info",n)}function l(n){i("warning",n)}function f(n){i("error",n)}function d(n){i("fatal",n)}var s=function n(e,t){(function(n,e){if(!(n instanceof e))throw new TypeError("Cannot call a class as a function")})(this,n),t=t||-1,this.Callback=function(n){return e.apply(null,n),-1!==t&&0===(t-=1)}},v={};function p(n,e,t){v[n]=v[n]||[];var r=new s(e,t);v[n].push(r)}function w(n){a("event",{name:n,data:JSON.stringify([].slice.apply(arguments).slice(1))})}var y={};var g={};var m=window.crypto?function(){var n=new Uint32Array(1);return window.crypto.getRandomValues(n)[0]}:function(){return 9007199254740991*Math.random()};function b(n,e,t){return(null==t||null==t)&&(t=0),new Promise(function(r,o){var i;do{i=n+"-"+m()}while(g[i]);if(0<t)var c=setTimeout(function(){o(Error("Call to "+n+" timed out. Request ID: "+i))},t);g[i]={timeoutHandle:c,reject:o,resolve:r};try{a("call",{bindingName:n,data:JSON.stringify(e)},i)}catch(n){console.error(n)}})}var h=window.backend;function O(n){try{return new Function("var "+n),!0}catch(n){return!1}}function S(){return(S=Object.assign||function(n){for(var e,t=1;t<arguments.length;t++)for(var r in e=arguments[t])Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}).apply(this,arguments)}window.wails=window.wails||{},window.backend={};var k={NewBinding:function(n){var e=n.split(".").splice(1),t=e.pop(),r=function(n){var e=h;for(var t in n){var r=n[t];if(!O(r))return[null,new Error(r+" is not a valid javascript identifier.")];e[r]||(e[r]={}),e=e[r]}return[e,null]}(e),o=r[0],a=r[1];return null==a?void(o[t]=function(){function e(){var e=[].slice.call(arguments);return b(n,e,t)}var t=0;return e.setTimeout=function(n){t=n},e.getTimeout=function(){return t},e}()):a},Callback:function(n){var e;n=decodeURIComponent(n.replace(/\s+/g,"").replace(/[0-9a-f]{2}/g,"%$&"));try{e=JSON.parse(n)}catch(e){return c("Invalid JSON passed to callback: "+e.message),void c("Message: "+n)}var t=e.callbackid,r=g[t];return r?(clearTimeout(r.timeoutHandle),delete g[t],e.error?r.reject(e.error):r.resolve(e.data)):void console.error("Callback '".concat(t,"' not registed!!!"))},Notify:function(n,e){if(v[n]){for(var t=v[n].slice(),r=0;r<v[n].length;r+=1){var o=v[n][r],a=[];if(e)try{a=JSON.parse(e)}catch(e){f("Invalid JSON data sent to notify. Event name = "+n)}o.Callback(a)&&t.splice(r,1)}v[n]=t}},AddScript:function(n,e){var t=document.createElement("script");t.text=n,document.body.appendChild(t),e&&w(e)},InjectCSS:function(n){var e=document.createElement("style");e.setAttribute("type","text/css"),e.styleSheet?e.styleSheet.cssText=n:e.appendChild(document.createTextNode(n)),(document.head||document.getElementsByTagName("head")[0]).appendChild(e)}},j={Log:r,Event:{On:function(n,e){p(n,e)},Emit:w,Heartbeat:function(n,e,t){var r=null;y[n]=function(){clearInterval(r),t()},r=setInterval(function(){w(n)},e)},Acknowledge:function(n){if(!y[n])throw new f("Cannot acknowledge unknown heartbeat '".concat(n,"'"));y[n]()}},_:k};S(window.wails,j),w("wails:loaded")}]);

7647
runtime/js/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
runtime/js/package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "wails-runtime",
"version": "1.0.0",
"description": "The Javascript Wails Runtime",
"main": "index.js",
"scripts": {
"build": "eslint src/ && webpack --config webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/runtime.git"
},
"keywords": [
"Wails",
"Go",
"Javascript",
"Runtime"
],
"browserslist": [
"> 5%",
"IE 9"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/runtime/issues"
},
"homepage": "https://github.com/wailsapp/runtime#readme",
"devDependencies": {
"@babel/cli": "^7.5.0",
"@babel/core": "^7.5.4",
"@babel/plugin-transform-object-assign": "^7.2.0",
"@babel/preset-env": "^7.5.4",
"babel-loader": "^8.0.6",
"babel-preset-minify": "^0.5.0",
"core-js": "^3.1.4",
"eslint": "^6.0.1",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.5"
}
}

View File

@@ -0,0 +1,94 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
import { Call } from './calls';
var bindingsBasePath = window.backend;
// Determines if the given identifier is valid Javascript
function isValidIdentifier(name) {
// Don't xss yourself :-)
try {
new Function('var ' + name);
return true;
} catch (e) {
return false;
}
}
// Creates the path given in the bindings path
function addBindingPath(pathSections) {
// Start at the base path
var currentPath = bindingsBasePath;
// for each section of the given path
for (var sectionIndex in pathSections) {
var section = pathSections[sectionIndex];
// Is section a valid javascript identifier?
if (!isValidIdentifier(section)) {
var errMessage = section + ' is not a valid javascript identifier.';
var err = new Error(errMessage);
return [null, err];
}
// Add if doesn't exist
if (!currentPath[section]) {
currentPath[section] = {};
}
// update current path to new path
currentPath = currentPath[section];
}
return [currentPath, null];
}
export function NewBinding(bindingName) {
// Get all the sections of the binding
var bindingSections = bindingName.split('.').splice(1);
// Get the actual function/method call name
var callName = bindingSections.pop();
// Add path to binding
var bs = addBindingPath(bindingSections);
var pathToBinding = bs[0];
var err = bs[1];
if (err != null) {
// We need to return an error
return err;
}
// Add binding call
pathToBinding[callName] = function () {
// No timeout by default
var timeout = 0;
// Actual function
function dynamic() {
var args = [].slice.call(arguments);
return Call(bindingName, args, timeout);
}
// Allow setting timeout to function
dynamic.setTimeout = function (newTimeout) {
timeout = newTimeout;
};
// Allow getting timeout to function
dynamic.getTimeout = function () {
return timeout;
};
return dynamic;
}();
}

19
runtime/js/src/browser.js Normal file
View File

@@ -0,0 +1,19 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
import { SystemCall } from './calls';
export function OpenURL(url) {
return SystemCall('Browser.OpenURL', url);
}
export function OpenFile(filename) {
return SystemCall('Browser.OpenFile', filename);
}

116
runtime/js/src/calls.js Normal file
View File

@@ -0,0 +1,116 @@
import { Debug } from './log';
import { SendMessage } from './ipc'
var callbacks = {};
// AwesomeRandom
function cryptoRandom() {
var array = new Uint32Array(1);
return window.crypto.getRandomValues(array)[0];
}
// LOLRandom
function basicRandom() {
return Math.random() * 9007199254740991;
}
// Pick one based on browser capability
var randomFunc;
if (window.crypto) {
randomFunc = cryptoRandom;
} else {
randomFunc = basicRandom;
}
// Call sends a message to the backend to call the binding with the
// given data. A promise is returned and will be completed when the
// backend responds. This will be resolved when the call was successful
// or rejected if an error is passed back.
// There is a timeout mechanism. If the call doesn't respond in the given
// time (in milliseconds) then the promise is rejected.
export function Call(bindingName, data, timeout) {
// Timeout infinite by default
if (timeout == null || timeout == undefined) {
timeout = 0;
}
// Create a promise
return new Promise(function (resolve, reject) {
// Create a unique callbackID
var callbackID;
do {
callbackID = bindingName + '-' + randomFunc();
} while (callbacks[callbackID]);
// Set timeout
if (timeout > 0) {
var timeoutHandle = setTimeout(function () {
reject(Error('Call to ' + bindingName + ' timed out. Request ID: ' + callbackID));
}, timeout);
}
// Store callback
callbacks[callbackID] = {
timeoutHandle: timeoutHandle,
reject: reject,
resolve: resolve
};
try {
const payload = {
bindingName: bindingName,
data: JSON.stringify(data),
}
// Make the call
SendMessage('call', payload, callbackID)
} catch (e) {
// eslint-disable-next-line
console.error(e);
}
});
}
// Called by the backend to return data to a previously called
// binding invocation
export function Callback(incomingMessage) {
// Decode the message - Credit: https://stackoverflow.com/a/13865680
incomingMessage = decodeURIComponent(incomingMessage.replace(/\s+/g, '').replace(/[0-9a-f]{2}/g, '%$&'));
// Parse the message
var message;
try {
message = JSON.parse(incomingMessage);
} catch (e) {
Debug('Invalid JSON passed to callback: ' + e.message);
Debug('Message: ' + incomingMessage);
return;
}
var callbackID = message.callbackid;
var callbackData = callbacks[callbackID];
if (!callbackData) {
// eslint-disable-next-line
console.error(`Callback '${callbackID}' not registed!!!`);
return;
}
clearTimeout(callbackData.timeoutHandle);
delete callbacks[callbackID];
if (message.error) {
return callbackData.reject(message.error);
}
return callbackData.resolve(message.data);
}
// systemCall is used to call wails methods from the frontend
export function SystemCall(method, data) {
return Call('.wails.' + method, data);
}

138
runtime/js/src/events.js Normal file
View File

@@ -0,0 +1,138 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
import { Error } from './log';
import { SendMessage } from './ipc';
// Defines a single listener with a maximum number of times to callback
class Listener {
constructor(callback, maxCallbacks) {
// Default of -1 means infinite
maxCallbacks = maxCallbacks || -1;
// Callback invokes the callback with the given data
// Returns true if this listener should be destroyed
this.Callback = (data) => {
callback.apply(null, data);
// If maxCallbacks is infinite, return false (do not destroy)
if (maxCallbacks === -1) {
return false;
}
// Decrement maxCallbacks. Return true if now 0, otherwise false
maxCallbacks -= 1;
return maxCallbacks === 0;
};
}
}
var eventListeners = {};
// Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
export function OnMultiple(eventName, callback, maxCallbacks) {
eventListeners[eventName] = eventListeners[eventName] || [];
const thisListener = new Listener(callback, maxCallbacks);
eventListeners[eventName].push(thisListener);
}
// Registers an event listener that will be invoked every time the event is emitted
export function On(eventName, callback) {
OnMultiple(eventName, callback);
}
// Registers an event listener that will be invoked once then destroyed
export function Once(eventName, callback) {
OnMultiple(eventName, callback, 1);
}
// Notify informs frontend listeners that an event was emitted with the given data
export function Notify(eventName, data) {
// Check if we have any listeners for this event
if (eventListeners[eventName]) {
// Keep a list of listener indexes to destroy
const newEventListenerList = eventListeners[eventName].slice();
// Iterate listeners
for (let count = 0; count < eventListeners[eventName].length; count += 1) {
// Get next listener
const listener = eventListeners[eventName][count];
// Parse data if we have it
var parsedData = [];
if (data) {
try {
parsedData = JSON.parse(data);
} catch (e) {
Error('Invalid JSON data sent to notify. Event name = ' + eventName);
}
}
// Do the callback
const destroy = listener.Callback(parsedData);
if (destroy) {
// if the listener indicated to destroy itself, add it to the destroy list
newEventListenerList.splice(count, 1);
}
}
// Update callbacks with new list of listners
eventListeners[eventName] = newEventListenerList;
}
}
// Emit an event with the given name and data
export function Emit(eventName) {
// Calculate the data
var data = JSON.stringify([].slice.apply(arguments).slice(1));
// Notify backend
const payload = {
name: eventName,
data: data,
}
SendMessage('event', payload)
}
const heartbeatCallbacks = {};
// Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
// the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
export function Heartbeat(eventName, timeInMilliseconds, callback) {
// Declare interval variable
let interval = null;
// Setup callback
function dynamicCallback() {
// Kill interval
clearInterval(interval);
// Callback
callback();
}
// Register callback
heartbeatCallbacks[eventName] = dynamicCallback;
// Start emitting the event
interval = setInterval(function () {
Emit(eventName);
}, timeInMilliseconds);
}
export function Acknowledge(eventName) {
// If we are waiting for acknowledgement for this event type
if (heartbeatCallbacks[eventName]) {
// Acknowledge!
heartbeatCallbacks[eventName]();
} else {
throw new Error(`Cannot acknowledge unknown heartbeat '${eventName}'`);
}
}

28
runtime/js/src/ipc.js Normal file
View File

@@ -0,0 +1,28 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
// var Invoke = window.external.invoke;
var Invoke;
if (window && window.external && window.external.invoke) {
Invoke = window.external.invoke;
} else {
Invoke = console.log;
}
export function SendMessage(type, payload, callbackID) {
const message = {
type,
callbackID,
payload
};
Invoke(JSON.stringify(message));
}

44
runtime/js/src/log.js Normal file
View File

@@ -0,0 +1,44 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
import { SendMessage } from './ipc';
// Sends a log message to the backend with the given
// level + message
function sendLogMessage(level, message) {
// Log Message
const payload = {
level: level,
message: message,
}
SendMessage('log', payload)
}
export function Debug(message) {
sendLogMessage('debug', message);
}
export function Info(message) {
sendLogMessage('info', message);
}
export function Warning(message) {
sendLogMessage('warning', message);
}
export function Error(message) {
sendLogMessage('error', message);
}
export function Fatal(message) {
sendLogMessage('fatal', message);
}

45
runtime/js/src/main.js Normal file
View File

@@ -0,0 +1,45 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
import * as Log from './log';
import { On, Emit, Notify, Heartbeat, Acknowledge } from './events';
import { NewBinding } from './bindings';
import { Callback } from './calls';
import { AddScript, InjectCSS } from './utils';
// Initialise global if not already
window.wails = window.wails || {};
window.backend = {};
// Setup internal calls
var internal = {
NewBinding,
Callback,
Notify,
AddScript,
InjectCSS
}
// Setup runtime structure
var runtime = {
Log,
Event: {
On,
Emit,
Heartbeat,
Acknowledge,
},
_: internal,
}
// Augment global
Object.assign(window.wails, runtime);
// Emit loaded event
Emit('wails:loaded');

33
runtime/js/src/utils.js Normal file
View File

@@ -0,0 +1,33 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
import { Emit } from './events';
export function AddScript(js, callbackID) {
var script = document.createElement('script');
script.text = js;
document.body.appendChild(script);
if (callbackID) {
Emit(callbackID);
}
}
// Adapted from webview - thanks zserge!
export function InjectCSS(css) {
var elem = document.createElement('style');
elem.setAttribute('type', 'text/css');
if (elem.styleSheet) {
elem.styleSheet.cssText = css;
} else {
elem.appendChild(document.createTextNode(css));
}
var head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(elem);
}

View File

@@ -0,0 +1,38 @@
/* eslint-disable */
const path = require('path');
module.exports = {
entry: './src/main',
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'wails.js'
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
plugins: ['@babel/plugin-transform-object-assign'],
presets: [
[
'@babel/preset-env',
{
'useBuiltIns': 'entry',
'corejs': {
'version': 3,
'proposals': true
}
}
], ['minify']
]
}
}
}
]
}
};

View File

@@ -1,25 +0,0 @@
package wails
import "github.com/pkg/browser"
// GlobalRuntimeBrowser is the global instance of the RuntimeBrowser object
// Why? Because we need to use it in both the runtime and from the frontend
var GlobalRuntimeBrowser = newRuntimeBrowser()
// RuntimeBrowser exposes browser methods to the runtime
type RuntimeBrowser struct {
}
func newRuntimeBrowser() *RuntimeBrowser {
return &RuntimeBrowser{}
}
// OpenURL opens the given url in the system's default browser
func (r *RuntimeBrowser) OpenURL(url string) error {
return browser.OpenURL(url)
}
// OpenFile opens the given file in the system's default browser
func (r *RuntimeBrowser) OpenFile(filePath string) error {
return browser.OpenFile(filePath)
}

View File

@@ -1,28 +0,0 @@
package wails
// RuntimeDialog exposes an interface to native dialogs
type RuntimeDialog struct {
renderer Renderer
}
// newRuntimeDialog creates a new RuntimeDialog struct
func newRuntimeDialog(renderer Renderer) *RuntimeDialog {
return &RuntimeDialog{
renderer: renderer,
}
}
// SelectFile prompts the user to select a file
func (r *RuntimeDialog) SelectFile() string {
return r.renderer.SelectFile()
}
// SelectDirectory prompts the user to select a directory
func (r *RuntimeDialog) SelectDirectory() string {
return r.renderer.SelectDirectory()
}
// SelectSaveFile prompts the user to select a file for saving
func (r *RuntimeDialog) SelectSaveFile() string {
return r.renderer.SelectSaveFile()
}

View File

@@ -1,22 +0,0 @@
package wails
// RuntimeEvents exposes the events interface
type RuntimeEvents struct {
eventManager *eventManager
}
func newRuntimeEvents(eventManager *eventManager) *RuntimeEvents {
return &RuntimeEvents{
eventManager: eventManager,
}
}
// On pass through
func (r *RuntimeEvents) On(eventName string, callback func(optionalData ...interface{})) {
r.eventManager.On(eventName, callback)
}
// Emit pass through
func (r *RuntimeEvents) Emit(eventName string, optionalData ...interface{}) {
r.eventManager.Emit(eventName, optionalData...)
}

View File

@@ -1,16 +0,0 @@
package wails
import homedir "github.com/mitchellh/go-homedir"
// RuntimeFileSystem exposes file system utilities to the runtime
type RuntimeFileSystem struct {
}
func newRuntimeFileSystem() *RuntimeFileSystem {
return &RuntimeFileSystem{}
}
// HomeDir returns the user's home directory
func (r *RuntimeFileSystem) HomeDir() (string, error) {
return homedir.Dir()
}

View File

@@ -1,14 +0,0 @@
package wails
// RuntimeLog exposes the logging interface to the runtime
type RuntimeLog struct {
}
func newRuntimeLog() *RuntimeLog {
return &RuntimeLog{}
}
// New creates a new logger
func (r *RuntimeLog) New(prefix string) *CustomLogger {
return newCustomLogger(prefix)
}

9
scripts/build.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Build runtime
cd runtime/js
npm run build
cd ../..
mewn

View File

@@ -10,6 +10,14 @@ package cmd
// Version - Wails version // Version - Wails version
const Version = "${TAG}" const Version = "${TAG}"
EOF EOF
# Build runtime
cd runtime/js
npm run build
cd ../..
mewn
git add cmd/version.go git add cmd/version.go
git commit cmd/version.go -m "Bump to ${TAG}" git commit cmd/version.go -m "Bump to ${TAG}"
git tag ${TAG} git tag ${TAG}

View File

@@ -1,12 +0,0 @@
package wails
import (
"strings"
)
func escapeJS(js string) (string, error) {
result := strings.Replace(js, "\\", "\\\\", -1)
result = strings.Replace(result, "'", "\\'", -1)
result = strings.Replace(result, "\n", "\\n", -1)
return result, nil
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
!function(){var e;function n(e){try{return new Function("var "+e),!0}catch(e){return!1}}window.wails=window.wails||{},window.backend={},e=window.crypto?function(){var e=new Uint32Array(1);return window.crypto.getRandomValues(e)[0]}:function(){return 9007199254740991*Math.random()};var t=window.backend;var r={};function i(n,t,i){return null!=i&&null!=i||(i=0),new Promise(function(a,o){var l;do{l=n+"-"+e()}while(r[l]);if(i>0)var c=setTimeout(function(){o(Error("Call to "+n+" timed out. Request ID: "+l))},i);r[l]={timeoutHandle:c,reject:o,resolve:a};try{var s=JSON.stringify(t),u={type:"call",callbackid:l,payload:{bindingName:n,data:s}},d=JSON.stringify(u);external.invoke(d)}catch(e){console.error(e)}})}function a(e,n){return i(".wails."+e,n)}var o={};function l(e,n){o[e]=o[e]||[],o[e].push(n)}function c(e){var n={type:"event",payload:{name:e,data:JSON.stringify([].slice.apply(arguments).slice(1))}};external.invoke(JSON.stringify(n))}function s(e,n){var t=n[0].toUpperCase()+n.substring(1);return function(r,i){return console.warn("Method events."+n+" has been deprecated. Please use Events."+t),e(r,i)}}function u(e,n){n={type:"log",payload:{level:e,message:n}},external.invoke(JSON.stringify(n))}function d(e,n){var t=n[0].toUpperCase()+n.substring(1);return function(r){return console.warn("Method Log."+n+" has been deprecated. Please use Log."+t),e(r)}}function w(e){u("debug",e)}function f(e){u("info",e)}function p(e){u("warning",e)}function v(e){u("error",e)}function g(e){u("fatal",e)}window.wails.events={emit:s(c,"emit"),on:s(l,"on")},window.wails.Events={Emit:c,On:l},window.wails.Browser={OpenURL:function(e){return a("Browser.OpenURL",e)},OpenFile:function(e){return a("Browser.OpenFile",e)}},window.wails.log={debug:d(w,"debug"),info:d(f,"info"),warning:d(p,"warning"),error:d(v,"error"),fatal:d(g,"fatal")},window.wails.Log={Debug:w,Info:f,Warning:p,Error:v,Fatal:g},window.wails._={newBinding:function(e){var r=e.split(".").splice(1),a=r.pop(),o=function(e){var r=t;for(var i in e){var a=e[i];if(!n(a))return[null,new Error(a+" is not a valid javascript identifier.")];r[a]||(r[a]={}),r=r[a]}return[r,null]}(r),l=o[0],c=o[1];if(null!=c)return c;l[a]=function(){var n=0;function t(){var t=[].slice.call(arguments);return i(e,t,n)}return t.setTimeout=function(e){n=e},t.getTimeout=function(){return n},t}()},callback:function(e){var n;e=decodeURIComponent(e.replace(/\s+/g,"").replace(/[0-9a-f]{2}/g,"%$&"));try{n=JSON.parse(e)}catch(n){return window.wails.Log.Debug("Invalid JSON passed to callback: "+n.message),void window.wails.Log.Debug("Message: "+e)}var t=n.callbackid,i=r[t];if(i)return clearTimeout(i.timeoutHandle),delete r[t],n.error?i.reject(n.error):i.resolve(n.data);console.error("Callback '"+t+"' not registed!!!")},notify:function(e,n){o[e]&&o[e].forEach(function(t){var r=[];if(n)try{r=JSON.parse(n)}catch(n){window.wails.Log.Error("Invalid JSON data sent to notify. Event name = "+e)}t.apply(null,r)})},sendLogMessage:u,callbacks:r,injectCSS:function(e){var n=document.createElement("style");n.setAttribute("type","text/css"),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e)),(document.head||document.getElementsByTagName("head")[0]).appendChild(n)},addScript:function(e,n){var t=document.createElement("script");t.text=e,document.body.appendChild(t),window.wails.Events.Emit(n)}},window.wails.Events.Emit("wails:loaded")}();

View File

@@ -1,404 +0,0 @@
// Wails runtime JS
(function () {
window.wails = window.wails || {};
window.backend = {};
/****************** Utility Functions ************************/
// -------------- Random --------------
// AwesomeRandom
function cryptoRandom() {
var array = new Uint32Array(1);
return window.crypto.getRandomValues(array)[0];
}
// LOLRandom
function basicRandom() {
return Math.random() * 9007199254740991;
}
// Pick one based on browser capability
var randomFunc;
if (window.crypto) {
randomFunc = cryptoRandom;
} else {
randomFunc = basicRandom;
}
// -------------- Identifiers ---------------
function isValidIdentifier(name) {
// Don't xss yourself :-)
try {
new Function('var ' + name);
return true;
} catch (e) {
return false;
}
}
// -------------- JS ----------------
function addScript(js, callbackID) {
var script = document.createElement('script');
script.text = js;
document.body.appendChild(script);
window.wails.Events.Emit(callbackID);
}
// -------------- CSS ---------------
// Adapted from webview - thanks zserge!
function injectCSS(css) {
var elem = document.createElement('style');
elem.setAttribute('type', 'text/css');
if (elem.styleSheet) {
elem.styleSheet.cssText = css;
} else {
elem.appendChild(document.createTextNode(css));
}
var head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(elem);
}
/************************* Bindings *************************/
var bindingsBasePath = window.backend;
// Creates the path given in the bindings path
function addBindingPath(pathSections) {
// Start at the base path
var currentPath = bindingsBasePath;
// for each section of the given path
for (var sectionIndex in pathSections) {
var section = pathSections[sectionIndex];
// Is section a valid javascript identifier?
if (!isValidIdentifier(section)) {
var errMessage = section + ' is not a valid javascript identifier.';
var err = new Error(errMessage);
return [null, err];
}
// Add if doesn't exist
if (!currentPath[section]) {
currentPath[section] = {};
}
// update current path to new path
currentPath = currentPath[section];
}
return [currentPath, null];
}
function newBinding(bindingName) {
// Get all the sections of the binding
var bindingSections = bindingName.split('.').splice(1);
// Get the actual function/method call name
var callName = bindingSections.pop();
// Add path to binding
var bs = addBindingPath(bindingSections);
var pathToBinding = bs[0];
var err = bs[1];
if (err != null) {
// We need to return an error
return err;
}
// Add binding call
pathToBinding[callName] = function () {
// No timeout by default
var timeout = 0;
// Actual function
function dynamic() {
var args = [].slice.call(arguments);
return call(bindingName, args, timeout);
}
// Allow setting timeout to function
dynamic.setTimeout = function (newTimeout) {
timeout = newTimeout;
};
// Allow getting timeout to function
dynamic.getTimeout = function () {
return timeout;
};
return dynamic;
}();
}
/************************************************************/
/*************************** Calls **************************/
var callbacks = {};
// Call sends a message to the backend to call the binding with the
// given data. A promise is returned and will be completed when the
// backend responds. This will be resolved when the call was successful
// or rejected if an error is passed back.
// There is a timeout mechanism. If the call doesn't respond in the given
// time (in milliseconds) then the promise is rejected.
function call(bindingName, data, timeout) {
// Timeout infinite by default
if (timeout == null || timeout == undefined) {
timeout = 0;
}
// Create a promise
return new Promise(function (resolve, reject) {
// Create a unique callbackID
var callbackID;
do {
callbackID = bindingName + '-' + randomFunc();
} while (callbacks[callbackID]);
// Set timeout
if (timeout > 0) {
var timeoutHandle = setTimeout(function () {
reject(Error('Call to ' + bindingName + ' timed out. Request ID: ' + callbackID));
}, timeout);
}
// Store callback
callbacks[callbackID] = {
timeoutHandle: timeoutHandle,
reject: reject,
resolve: resolve
};
try {
var payloaddata = JSON.stringify(data);
// Create the message
var message = {
type: 'call',
callbackid: callbackID,
payload: {
bindingName: bindingName,
data: payloaddata,
}
};
// Make the call
var payload = JSON.stringify(message);
external.invoke(payload);
} catch (e) {
// eslint-disable-next-line
console.error(e);
}
});
}
// systemCall is used to call wails methods from the frontend
function systemCall(method, data) {
return call('.wails.' + method, data);
}
// Called by the backend to return data to a previously called
// binding invocation
function callback(incomingMessage) {
// Decode the message - Credit: https://stackoverflow.com/a/13865680
incomingMessage = decodeURIComponent(incomingMessage.replace(/\s+/g, '').replace(/[0-9a-f]{2}/g, '%$&'));
// Parse the message
var message;
try {
message = JSON.parse(incomingMessage);
} catch (e) {
window.wails.Log.Debug('Invalid JSON passed to callback: ' + e.message);
window.wails.Log.Debug('Message: ' + incomingMessage);
return;
}
var callbackID = message.callbackid;
var callbackData = callbacks[callbackID];
if (!callbackData) {
// eslint-disable-next-line
console.error('Callback \'' + callbackID + '\' not registed!!!');
return;
}
clearTimeout(callbackData.timeoutHandle);
delete callbacks[callbackID];
if (message.error) {
return callbackData.reject(message.error);
}
return callbackData.resolve(message.data);
}
/************************************************************/
/************************** Events **************************/
var eventListeners = {};
// Registers event listeners
function on(eventName, callback) {
eventListeners[eventName] = eventListeners[eventName] || [];
eventListeners[eventName].push(callback);
}
// notify informs frontend listeners that an event was emitted with the given data
function notify(eventName, data) {
if (eventListeners[eventName]) {
eventListeners[eventName].forEach(function (element) {
var parsedData = [];
// Parse data if we have it
if (data) {
try {
parsedData = JSON.parse(data);
} catch (e) {
window.wails.Log.Error('Invalid JSON data sent to notify. Event name = ' + eventName);
}
}
element.apply(null, parsedData);
});
}
}
// emit an event with the given name and data
function emit(eventName) {
// Calculate the data
var data = JSON.stringify([].slice.apply(arguments).slice(1));
// Notify backend
var message = {
type: 'event',
payload: {
name: eventName,
data: data,
}
};
external.invoke(JSON.stringify(message));
}
function deprecatedEventsFunction(fn, oldName) {
var newName = oldName[0].toUpperCase() + oldName.substring(1);
return function (eventName, eventData) {
// eslint-disable-next-line
console.warn('Method events.' + oldName + ' has been deprecated. Please use Events.' + newName);
return fn(eventName, eventData);
};
}
// Deprecated Events calls
window.wails.events = {
emit: deprecatedEventsFunction(emit, 'emit'),
on: deprecatedEventsFunction(on, 'on'),
};
// Events calls
window.wails.Events = {
Emit: emit,
On: on
};
/************************************************************/
/************************* Browser **************************/
function OpenURL(url) {
return systemCall('Browser.OpenURL', url);
}
function OpenFile(filename) {
return systemCall('Browser.OpenFile', filename);
}
window.wails.Browser = {
OpenURL,
OpenFile,
};
/************************* Logging **************************/
// Sends a log message to the backend with the given
// level + message
function sendLogMessage(level, message) {
// Log Message
message = {
type: 'log',
payload: {
level: level,
message: message,
}
};
external.invoke(JSON.stringify(message));
}
function deprecatedLogFunction(fn, oldName) {
var newName = oldName[0].toUpperCase() + oldName.substring(1);
return function (message) {
// eslint-disable-next-line
console.warn('Method Log.' + oldName + ' has been deprecated. Please use Log.' + newName);
return fn(message);
};
}
function logDebug(message) {
sendLogMessage('debug', message);
}
function logInfo(message) {
sendLogMessage('info', message);
}
function logWarning(message) {
sendLogMessage('warning', message);
}
function logError(message) {
sendLogMessage('error', message);
}
function logFatal(message) {
sendLogMessage('fatal', message);
}
window.wails.log = {
debug: deprecatedLogFunction(logDebug, 'debug'),
info: deprecatedLogFunction(logInfo, 'info'),
warning: deprecatedLogFunction(logWarning, 'warning'),
error: deprecatedLogFunction(logError, 'error'),
fatal: deprecatedLogFunction(logFatal, 'fatal'),
};
window.wails.Log = {
Debug: logDebug,
Info: logInfo,
Warning: logWarning,
Error: logError,
Fatal: logFatal,
};
/************************** Exports *************************/
window.wails._ = {
newBinding: newBinding,
callback: callback,
notify: notify,
sendLogMessage: sendLogMessage,
callbacks: callbacks,
injectCSS: injectCSS,
addScript: addScript,
};
/************************************************************/
// Notify backend that the runtime has finished loading
window.wails.Events.Emit('wails:loaded');
})();