Merge pull request #157 from wailsapp/runtime-refactor

Runtime refactor
This commit is contained in:
Lea Anthony 2019-07-13 22:27:36 +10:00 committed by GitHub
commit 29535c10a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 9144 additions and 929 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

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
runtime/js/dist/wails.js

View file

@ -1,4 +1,4 @@
module.exports = {
{
"env": {
"browser": true,
"es6": true
@ -26,4 +26,4 @@ module.exports = {
"always"
]
}
};
}

2
.gitignore vendored
View file

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

6
.hound.yml Normal file
View file

@ -0,0 +1,6 @@
jshint:
config_file: .jshintrc
eslint:
enabled: true
config_file: .eslintrc
ignore_file: .eslintignore

51
app.go
View file

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

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
bridgeAssets := mewn.Group("../wailsruntimeassets/bridge/")
bridgeAssets := mewn.Group("../runtime/bridge/")
bridgeFileData := bridgeAssets.Bytes(bridgeFile)
bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, projectOptions.FrontEnd.Bridge, "wailsbridge.js")
err := fs.CreateFile(bridgeFileTarget, bridgeFileData)

View file

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

View file

@ -1,11 +1,5 @@
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 {
@ -18,7 +12,51 @@ type AppConfig struct {
Colour string
Resizable bool
DisableInspector bool
isHTMLFragment bool
}
// GetWidth returns the desired width
func (a *AppConfig) GetWidth() int {
return a.Width
}
// GetHeight returns the desired height
func (a *AppConfig) GetHeight() int {
return a.Height
}
// GetTitle returns the desired window title
func (a *AppConfig) GetTitle() string {
return a.Title
}
// GetDefaultHTML returns the desired window title
func (a *AppConfig) GetDefaultHTML() string {
return a.defaultHTML
}
// GetResizable returns true if the window should be resizable
func (a *AppConfig) GetResizable() bool {
return a.Resizable
}
// GetDisableInspector returns true if the inspector should be disabled
func (a *AppConfig) GetDisableInspector() bool {
return a.DisableInspector
}
// GetColour returns the colour
func (a *AppConfig) GetColour() string {
return a.Colour
}
// GetCSS returns the user CSS
func (a *AppConfig) GetCSS() string {
return a.CSS
}
// GetJS returns the user Javascript
func (a *AppConfig) GetJS() string {
return a.JS
}
func (a *AppConfig) merge(in *AppConfig) error {
@ -28,32 +66,6 @@ func (a *AppConfig) merge(in *AppConfig) error {
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
@ -76,14 +88,13 @@ func (a *AppConfig) merge(in *AppConfig) error {
}
// Creates the default configuration
func newAppConfig(userConfig *AppConfig) (*AppConfig, error) {
func newConfig(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 {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,31 +1,34 @@
package wails
package event
import (
"fmt"
"sync"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/lib/messages"
"github.com/wailsapp/wails/lib/interfaces"
)
// eventManager handles and processes events
type eventManager struct {
incomingEvents chan *eventData
// Manager handles and processes events
type Manager struct {
incomingEvents chan *messages.EventData
listeners map[string][]*eventListener
exit bool
log *CustomLogger
renderer Renderer // Messages will be dispatched to the frontend
log *logger.CustomLogger
renderer interfaces.Renderer // Messages will be dispatched to the frontend
}
// newEventManager creates a new event manager with a 100 event buffer
func newEventManager() *eventManager {
return &eventManager{
incomingEvents: make(chan *eventData, 100),
// NewManager creates a new event manager with a 100 event buffer
func NewManager() interfaces.EventManager {
return &Manager{
incomingEvents: make(chan *messages.EventData, 100),
listeners: make(map[string][]*eventListener),
exit: false,
log: newCustomLogger("Events"),
log: logger.NewCustomLogger("Events"),
}
}
// 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
}
@ -40,7 +43,7 @@ type eventListener struct {
}
// 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
if callback == nil {
@ -65,18 +68,19 @@ func (e *eventManager) addEventListener(eventName string, callback func(...inter
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)
e.addEventListener(eventName, callback, 0)
}
// Emit broadcasts the given event to the subscribed listeners
func (e *eventManager) Emit(eventName string, optionalData ...interface{}) {
e.incomingEvents <- &eventData{Name: eventName, Data: optionalData}
func (e *Manager) Emit(eventName string, optionalData ...interface{}) {
e.incomingEvents <- &messages.EventData{Name: eventName, Data: optionalData}
}
// Starts the event manager's queue processing
func (e *eventManager) start(renderer Renderer) {
// Start the event manager's queue processing
func (e *Manager) Start(renderer interfaces.Renderer) {
e.log.Info("Starting")
@ -95,7 +99,7 @@ func (e *eventManager) start(renderer Renderer) {
// TODO: Listen for application exit
select {
case event := <-e.incomingEvents:
e.log.DebugFields("Got Event", Fields{
e.log.DebugFields("Got Event", logger.Fields{
"data": event.Data,
"name": event.Name,
})
@ -143,6 +147,6 @@ func (e *eventManager) start(renderer Renderer) {
wg.Wait()
}
func (e *eventManager) stop() {
func (e *Manager) stop() {
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
type Renderer interface {
Initialise(*AppConfig, *ipcManager, *eventManager) error
Initialise(AppConfig, IPCManager, EventManager) error
Run() error
// Binding
@ -10,7 +13,7 @@ type Renderer interface {
Callback(data string) error
// Events
NotifyEvent(eventData *eventData) error
NotifyEvent(eventData *messages.EventData) error
// Dialog Runtime
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 (
"fmt"
)
type callData struct {
BindingName string `json:"bindingName"`
Data string `json:"data,omitempty"`
}
"github.com/wailsapp/wails/lib/messages"
)
func init() {
messageProcessors["call"] = processCallData
@ -15,7 +12,7 @@ func init() {
func processCallData(message *ipcMessage) (*ipcMessage, error) {
var payload callData
var payload messages.CallData
// Decode binding call data
payloadMap := message.Payload.(map[string]interface{})

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,14 @@
package wails
package logger
import (
"os"
"strings"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus"
)
// Global logger reference
var logger = log.New()
var logger = logrus.New()
// Fields is used by the customLogger object to output
// fields along with a message
@ -17,26 +17,26 @@ type Fields map[string]interface{}
// Default options for the global logger
func init() {
logger.SetOutput(os.Stdout)
logger.SetLevel(log.DebugLevel)
logger.SetLevel(logrus.DebugLevel)
}
// Sets the log level to the given level
func setLogLevel(level string) {
// SetLogLevel sets the log level to the given level
func SetLogLevel(level string) {
switch strings.ToLower(level) {
case "info":
logger.SetLevel(log.InfoLevel)
logger.SetLevel(logrus.InfoLevel)
case "debug":
logger.SetLevel(log.DebugLevel)
logger.SetLevel(logrus.DebugLevel)
case "warn":
logger.SetLevel(log.WarnLevel)
logger.SetLevel(logrus.WarnLevel)
case "error":
logger.SetLevel(log.ErrorLevel)
logger.SetLevel(logrus.ErrorLevel)
case "fatal":
logger.SetLevel(log.FatalLevel)
logger.SetLevel(logrus.FatalLevel)
case "panic":
logger.SetLevel(log.PanicLevel)
logger.SetLevel(logrus.PanicLevel)
default:
logger.SetLevel(log.DebugLevel)
logger.SetLevel(logrus.DebugLevel)
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 (
"encoding/json"
@ -10,6 +10,9 @@ import (
"github.com/dchest/htmlmin"
"github.com/gorilla/websocket"
"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
@ -32,10 +35,10 @@ func (m messageType) toString() string {
// and renders the files over a websocket
type Headless struct {
// Common
log *CustomLogger
ipcManager *ipcManager
appConfig *AppConfig
eventManager *eventManager
log *logger.CustomLogger
ipcManager interfaces.IPCManager
appConfig interfaces.AppConfig
eventManager interfaces.EventManager
bindingCache []string
// Headless specific
@ -48,12 +51,12 @@ type Headless struct {
}
// 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.appConfig = appConfig
h.eventManager = eventManager
ipcManager.bindRenderer(h)
h.log = newCustomLogger("Bridge")
ipcManager.BindRenderer(h)
h.log = logger.NewCustomLogger("Bridge")
return nil
}
@ -83,7 +86,7 @@ func (h *Headless) injectCSS(css string) {
minifiedCSS = strings.Replace(minifiedCSS, "\\", "\\\\", -1)
minifiedCSS = strings.Replace(minifiedCSS, "'", "\\'", -1)
minifiedCSS = strings.Replace(minifiedCSS, "\n", " ", -1)
inject := fmt.Sprintf("wails._.injectCSS('%s')", minifiedCSS)
inject := fmt.Sprintf("wails._.InjectCSS('%s')", minifiedCSS)
h.evalJS(inject, cssMessage)
}
@ -117,7 +120,7 @@ func (h *Headless) start(conn *websocket.Conn) {
// set external.invoke
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)
// Inject bindings
@ -192,13 +195,13 @@ func (h *Headless) Callback(data string) error {
}
// 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!
var err error
if event == nil {
err = fmt.Errorf("Sent nil event to renderer.webViewRenderer")
logger.Error(err)
h.log.Error(err.Error())
return err
}
@ -215,14 +218,14 @@ func (h *Headless) NotifyEvent(event *eventData) error {
}
}
message := fmt.Sprintf("window.wails._.notify('%s','%s')", event.Name, data)
message := fmt.Sprintf("window.wails._.Notify('%s','%s')", event.Name, data)
return h.evalJS(message, notifyMessage)
}
// SetColour is unsupported for Headless but required
// for the Renderer interface
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
}
@ -241,7 +244,7 @@ func (h *Headless) UnFullscreen() {
// SetTitle is currently unsupported for Headless but required
// for the Renderer interface
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

File diff suppressed because one or more lines are too long

View file

@ -1,52 +1,61 @@
package wails
package renderer
import (
"encoding/json"
"fmt"
"math/rand"
"strings"
"sync"
"time"
"github.com/go-playground/colors"
"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"
)
// Window defines the main application window
// WebView defines the main webview application window
// Default values in []
type webViewRenderer struct {
type WebView struct {
window webview.WebView // The webview object
ipc *ipcManager
log *CustomLogger
config *AppConfig
eventManager *eventManager
ipc interfaces.IPCManager
log *logger.CustomLogger
config interfaces.AppConfig
eventManager interfaces.EventManager
bindingCache []string
}
// NewWebView returns a new WebView struct
func NewWebView() *WebView {
return &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
w.eventManager = eventManager
// Set up logger
w.log = newCustomLogger("WebView")
w.log = logger.NewCustomLogger("WebView")
// Set up the dispatcher function
w.ipc = ipc
ipc.bindRenderer(w)
ipc.BindRenderer(w)
// Save the config
w.config = config
// Create the WebView instance
w.window = webview.NewWebview(webview.Settings{
Width: config.Width,
Height: config.Height,
Title: config.Title,
Resizable: config.Resizable,
URL: config.defaultHTML,
Debug: !config.DisableInspector,
Width: config.GetWidth(),
Height: config.GetHeight(),
Title: config.GetTitle(),
Resizable: config.GetResizable(),
URL: config.GetDefaultHTML(),
Debug: !config.GetDisableInspector(),
ExternalInvokeCallback: func(_ webview.WebView, message string) {
w.ipc.Dispatch(message)
},
@ -55,7 +64,7 @@ func (w *webViewRenderer) Initialise(config *AppConfig, ipc *ipcManager, eventMa
// SignalManager.OnExit(w.Exit)
// Set colour
err := w.SetColour(config.Colour)
err := w.SetColour(config.GetColour())
if err != nil {
return err
}
@ -64,7 +73,8 @@ func (w *webViewRenderer) Initialise(config *AppConfig, ipc *ipcManager, eventMa
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)
if err != nil {
return err
@ -80,12 +90,12 @@ func (w *webViewRenderer) SetColour(colour string) error {
// evalJS evaluates the given js in the WebView
// 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)
if len(js) > 45 {
outputJS += "..."
}
w.log.DebugFields("Eval", Fields{"js": outputJS})
w.log.DebugFields("Eval", logger.Fields{"js": outputJS})
//
w.window.Dispatch(func() {
w.window.Eval(js)
@ -93,12 +103,21 @@ func (w *webViewRenderer) evalJS(js string) error {
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
// Do not call this from the main thread or you'll nuke your app because
// you won't get the callback.
func (w *webViewRenderer) evalJSSync(js string) error {
func (w *WebView) evalJSSync(js string) error {
minified, err := escapeJS(js)
if err != nil {
return err
}
@ -107,7 +126,7 @@ func (w *webViewRenderer) evalJSSync(js string) error {
if len(js) > 45 {
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))
var wg sync.WaitGroup
@ -122,7 +141,7 @@ func (w *webViewRenderer) evalJSSync(js string) error {
wg.Done()
exit = true
})
command := fmt.Sprintf("wails._.addScript('%s', '%s')", minified, ID)
command := fmt.Sprintf("wails._.AddScript('%s', '%s')", minified, ID)
w.window.Dispatch(func() {
w.window.Eval(command)
})
@ -137,24 +156,24 @@ func (w *webViewRenderer) evalJSSync(js string) error {
}
// 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.InjectCSS(css)
})
}
// Quit the window
func (w *webViewRenderer) Exit() {
// Exit closes the window
func (w *WebView) Exit() {
w.window.Exit()
}
// Run the window main loop
func (w *webViewRenderer) Run() error {
func (w *WebView) Run() error {
w.log.Info("Run()")
// Runtime assets
wailsRuntime := mewn.String("./wailsruntimeassets/default/wails.min.js")
wailsRuntime := mewn.String("../../runtime/js/dist/wails.js")
w.evalJS(wailsRuntime)
// Ping the wait channel when the wails runtime is loaded
@ -168,38 +187,30 @@ func (w *webViewRenderer) Run() error {
w.evalJSSync(binding)
}
// // Inject Framework
// if w.frameworkJS != "" {
// w.evalJSSync(w.frameworkJS)
// }
// if w.frameworkCSS != "" {
// w.injectCSS(w.frameworkCSS)
// }
// Inject user CSS
if w.config.CSS != "" {
outputCSS := fmt.Sprintf("%.45s", w.config.CSS)
if w.config.GetCSS() != "" {
outputCSS := fmt.Sprintf("%.45s", w.config.GetCSS())
if len(outputCSS) > 45 {
outputCSS += "..."
}
w.log.DebugFields("Inject User CSS", Fields{"css": outputCSS})
w.injectCSS(w.config.CSS)
w.log.DebugFields("Inject User CSS", logger.Fields{"css": outputCSS})
w.injectCSS(w.config.GetCSS())
} else {
// Use 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)
}
// Inject user JS
if w.config.JS != "" {
outputJS := fmt.Sprintf("%.45s", w.config.JS)
if w.config.GetJS() != "" {
outputJS := fmt.Sprintf("%.45s", w.config.GetJS())
if len(outputJS) > 45 {
outputJS += "..."
}
w.log.DebugFields("Inject User JS", Fields{"js": outputJS})
w.evalJSSync(w.config.JS)
w.log.DebugFields("Inject User JS", logger.Fields{"js": outputJS})
w.evalJSSync(w.config.GetJS())
}
// Emit that everything is loaded and ready
@ -213,14 +224,15 @@ func (w *webViewRenderer) Run() error {
return nil
}
// Binds the given method name with the front end
func (w *webViewRenderer) NewBinding(methodName string) error {
objectCode := fmt.Sprintf("window.wails._.newBinding('%s');", methodName)
// NewBinding registers a new binding with the frontend
func (w *WebView) NewBinding(methodName string) error {
objectCode := fmt.Sprintf("window.wails._.NewBinding('%s');", methodName)
w.bindingCache = append(w.bindingCache, objectCode)
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
// We need to run this on the main thread, however Dispatch is
@ -238,7 +250,8 @@ func (w *webViewRenderer) SelectFile() string {
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
// We need to run this on the main thread, however Dispatch is
// non-blocking so we launch this in a goroutine and wait for
@ -255,7 +268,8 @@ func (w *webViewRenderer) SelectDirectory() string {
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
// We need to run this on the main thread, however Dispatch is
// 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
func (w *webViewRenderer) Callback(data string) error {
callbackCMD := fmt.Sprintf("window.wails._.callback('%s');", data)
func (w *WebView) Callback(data string) error {
callbackCMD := fmt.Sprintf("window.wails._.Callback('%s');", data)
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!
var err error
if event == nil {
err = fmt.Errorf("Sent nil event to renderer.webViewRenderer")
logger.Error(err)
err = fmt.Errorf("Sent nil event to renderer.WebView")
w.log.Error(err.Error())
return err
}
@ -301,13 +316,13 @@ func (w *webViewRenderer) NotifyEvent(event *eventData) error {
}
}
message := fmt.Sprintf("wails._.notify('%s','%s')", event.Name, data)
message := fmt.Sprintf("wails._.Notify('%s','%s')", event.Name, data)
return w.evalJS(message)
}
// Window
func (w *webViewRenderer) Fullscreen() {
if w.config.Resizable == false {
// Fullscreen makes the main window go fullscreen
func (w *WebView) Fullscreen() {
if w.config.GetResizable() == false {
w.log.Warn("Cannot call Fullscreen() - App.Resizable = false")
return
}
@ -316,8 +331,9 @@ func (w *webViewRenderer) Fullscreen() {
})
}
func (w *webViewRenderer) UnFullscreen() {
if w.config.Resizable == false {
// UnFullscreen returns the window to the position prior to a fullscreen call
func (w *WebView) UnFullscreen() {
if w.config.GetResizable() == false {
w.log.Warn("Cannot call UnFullscreen() - App.Resizable = false")
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.SetTitle(title)
})
}
func (w *webViewRenderer) Close() {
// Close closes the window
func (w *WebView) Close() {
w.window.Dispatch(func() {
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

@ -26,12 +26,12 @@ window.wailsbridge = {
websocket: null,
callback: null,
overlayHTML:
'<div class="wails-reconnect-overlay"><div class="wails-reconnect-overlay-content"><div class="wails-reconnect-overlay-title">Wails Bridge</div><br><div class="wails-reconnect-overlay-loadingspinner"></div><br><div id="wails-reconnect-overlay-message">Waiting for backend</div></div></div>',
'<div class="wails-reconnect-overlay"><div class="wails-reconnect-overlay-content"><div class="wails-reconnect-overlay-title">Wails Bridge</div><br><div class="wails-reconnect-overlay-loadingspinner"></div><br><div id="wails-reconnect-overlay-message">Waiting for backend</div></div></div>',
overlayCSS:
'.wails-reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);font-family:sans-serif;display:none;z-index:999999}.wails-reconnect-overlay-content{padding:20px 30px;text-align:center;width:20em;position:relative;height:14em;border-radius:1em;margin:5% auto 0;background-color:#fff;box-shadow:1px 1px 20px 3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.wails-reconnect-overlay-title{font-size:2em}.wails-reconnect-overlay-message{font-size:1.3em}.wails-reconnect-overlay-loadingspinner{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#3E67EC #eee #eee;border-radius:50%;animation:loadingspin 1s linear infinite;margin:auto;padding:2.5em}@keyframes loadingspin{100%{transform:rotate(360deg)}}',
'.wails-reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);font-family:sans-serif;display:none;z-index:999999}.wails-reconnect-overlay-content{padding:20px 30px;text-align:center;width:20em;position:relative;height:14em;border-radius:1em;margin:5% auto 0;background-color:#fff;box-shadow:1px 1px 20px 3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.wails-reconnect-overlay-title{font-size:2em}.wails-reconnect-overlay-message{font-size:1.3em}.wails-reconnect-overlay-loadingspinner{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#3E67EC #eee #eee;border-radius:50%;animation:loadingspin 1s linear infinite;margin:auto;padding:2.5em}@keyframes loadingspin{100%{transform:rotate(360deg)}}',
log: function (message) {
// eslint-disable-next-line
console.log(
console.log(
'%c wails bridge %c ' + message + ' ',
'background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem',
'background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem'
@ -184,12 +184,12 @@ function startBridge() {
case 'b':
var binding = message.data.slice(1);
//log("Binding: " + binding)
window.wails._.newBinding(binding);
window.wails._.NewBinding(binding);
break;
// Call back
case 'c':
var callbackData = message.data.slice(1);
window.wails._.callback(callbackData);
window.wails._.Callback(callbackData);
break;
default:
window.wails.Log.Error('Unknown message type received: ' + message.data[0]);

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
type RuntimeWindow struct {
renderer Renderer
import "github.com/wailsapp/wails/lib/interfaces"
// Window exposes an interface for manipulating the window
type Window struct {
renderer interfaces.Renderer
}
func newRuntimeWindow(renderer Renderer) *RuntimeWindow {
return &RuntimeWindow{
func newWindow(renderer interfaces.Renderer) *Window {
return &Window{
renderer: renderer,
}
}
// 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)
}
// Fullscreen makes the window fullscreen
func (r *RuntimeWindow) Fullscreen() {
func (r *Window) Fullscreen() {
r.renderer.Fullscreen()
}
// UnFullscreen attempts to restore the window to the size/position before fullscreen
func (r *RuntimeWindow) UnFullscreen() {
func (r *Window) UnFullscreen() {
r.renderer.UnFullscreen()
}
// SetTitle sets the the window title
func (r *RuntimeWindow) SetTitle(title string) {
func (r *Window) SetTitle(title string) {
r.renderer.SetTitle(title)
}
// Close shuts down the window and therefore the app
func (r *RuntimeWindow) Close() {
func (r *Window) Close() {
r.renderer.Close()
}

280
runtime/js/.eslintrc Normal file
View file

@ -0,0 +1,280 @@
{
"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={};function o(n,e,t){!function(n){window&&window.external&&window.external.invoke?window.external.invoke(n):console.log("[No external.invoke] ".concat(n))}(JSON.stringify({type:n,callbackID:t,payload:e}))}function a(n,e){o("log",{level:n,message:e})}function i(n){a("debug",n)}function c(n){a("info",n)}function u(n){a("warning",n)}function l(n){a("error",n)}function f(n){a("fatal",n)}t.r(r),t.d(r,"Debug",function(){return i}),t.d(r,"Info",function(){return c}),t.d(r,"Warning",function(){return u}),t.d(r,"Error",function(){return l}),t.d(r,"Fatal",function(){return f});var d=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)}},s={};function p(n,e,t){s[n]=s[n]||[];var r=new d(e,t);s[n].push(r)}function v(n){o("event",{name:n,data:JSON.stringify([].slice.apply(arguments).slice(1))})}var w={};var y={};var g=window.crypto?function(){var n=new Uint32Array(1);return window.crypto.getRandomValues(n)[0]}:function(){return 9007199254740991*Math.random()};function m(n,e,t){return(null==t||null==t)&&(t=0),new Promise(function(r,a){var i;do{i=n+"-"+g()}while(y[i]);if(0<t)var c=setTimeout(function(){a(Error("Call to "+n+" timed out. Request ID: "+i))},t);y[i]={timeoutHandle:c,reject:a,resolve:r};try{o("call",{bindingName:n,data:JSON.stringify(e)},i)}catch(n){console.error(n)}})}function b(n){try{return new Function("var "+n),!0}catch(n){return!1}}function h(){return(h=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.backend={},window.wails=window.wails||{},window.backend={};var O={NewBinding:function(n){var e=[].concat(n.split(".").splice(1)),t=window.backend;if(1<e.length)for(var r,o=0;o<e.length-1;o+=1){if(!b(r=e[o]))return new Error("".concat(r," is not a valid javascript identifier."));t[r]={},t=t[r]}var a=e.pop();return b(a)?void(t[a]=function(){function e(){var e=[].slice.call(arguments);return m(n,e,t)}var t=0;return e.setTimeout=function(n){t=n},e.getTimeout=function(){return t},e}()):new Error("".concat(a," is not a valid javascript identifier."))},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 i("Invalid JSON passed to callback: "+e.message),void i("Message: "+n)}var t=e.callbackid,r=y[t];return r?(clearTimeout(r.timeoutHandle),delete y[t],e.error?r.reject(e.error):r.resolve(e.data)):void console.error("Callback '".concat(t,"' not registed!!!"))},Notify:function(n,e){if(s[n]){for(var t=s[n].slice(),r=0;r<s[n].length;r+=1){var o=s[n][r],a=[];if(e)try{a=JSON.parse(e)}catch(e){l("Invalid JSON data sent to notify. Event name = "+n)}o.Callback(a)&&t.splice(r,1)}s[n]=t}},AddScript:function(n,e){var t=document.createElement("script");t.text=n,document.body.appendChild(t),e&&v(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)}},S={Log:r,Events:{On:function(n,e){p(n,e)},Emit:v,Heartbeat:function(n,e,t){var r=null;w[n]=function(){clearInterval(r),t()},r=setInterval(function(){v(n)},e)},Acknowledge:function(n){if(!w[n])throw new l("Cannot acknowledge unknown heartbeat '".concat(n,"'"));w[n]()}},_:O};h(window.wails,S),v("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,80 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import { Call } from './calls';
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;
}
}
// eslint-disable-next-line max-lines-per-function
export function NewBinding(bindingName) {
// Get all the sections of the binding
var bindingSections = [].concat(bindingName.split('.').splice(1));
var pathToBinding = window.backend;
// Check if we have a path (IE Struct)
if (bindingSections.length > 1) {
// Iterate over binding sections, adding them to the window.backend object
for (let index = 0; index < bindingSections.length - 1; index += 1) {
const name = bindingSections[index];
// Is name a valid javascript identifier?
if (!isValidIdentifier(name)) {
return new Error(`${name} is not a valid javascript identifier.`);
}
pathToBinding[name] = {};
pathToBinding = pathToBinding[name];
}
}
// Get the actual function/method call name
const name = bindingSections.pop();
// Is name a valid javascript identifier?
if (!isValidIdentifier(name)) {
return new Error(`${name} is not a valid javascript identifier.`);
}
// Add binding call
pathToBinding[name] = 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;
}();
}

20
runtime/js/src/browser.js Normal file
View file

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

126
runtime/js/src/calls.js Normal file
View file

@ -0,0 +1,126 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
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) {
const error = `Invalid JSON passed to callback: ${e.message}. Message: ${incomingMessage}`;
Debug(error);
throw new Error(error);
}
var callbackID = message.callbackid;
var callbackData = callbacks[callbackID];
if (!callbackData) {
const error = `Callback '${callbackID}' not registed!!!`;
console.error(error); // eslint-disable-line
throw new Error(error);
}
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);
}

139
runtime/js/src/events.js Normal file
View file

@ -0,0 +1,139 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
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
*/
/* jshint esversion: 6 */
function Invoke(message) {
if (window && window.external && window.external.invoke) {
window.external.invoke(message);
} else {
console.log(`[No external.invoke] ${message}`); // eslint-disable-line
}
}
export function SendMessage(type, payload, callbackID) {
const message = {
type,
callbackID,
payload
};
Invoke(JSON.stringify(message));
}

45
runtime/js/src/log.js Normal file
View file

@ -0,0 +1,45 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
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);
}

46
runtime/js/src/main.js Normal file
View file

@ -0,0 +1,46 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
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,
Events: {
On,
Emit,
Heartbeat,
Acknowledge,
},
_: internal,
};
// Augment global
Object.assign(window.wails, runtime);
// Emit loaded event
Emit('wails:loaded');

34
runtime/js/src/utils.js Normal file
View file

@ -0,0 +1,34 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
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
const Version = "${TAG}"
EOF
# Build runtime
cd runtime/js
npm run build
cd ../..
mewn
git add cmd/version.go
git commit cmd/version.go -m "Bump to ${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');
})();