mirror of
https://github.com/taigrr/wails.git
synced 2026-04-17 12:15:02 -07:00
Compare commits
18 Commits
v2.0.0-alp
...
v2.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3501f4cb7 | ||
|
|
ee82cd25b7 | ||
|
|
bbb07e17d9 | ||
|
|
e6b40b55c4 | ||
|
|
7573f68df3 | ||
|
|
ceaacc7ba9 | ||
|
|
0e24f75753 | ||
|
|
82b9deeee4 | ||
|
|
cfa40b797f | ||
|
|
5aeb68acb7 | ||
|
|
b81101414f | ||
|
|
7ae89d04bb | ||
|
|
1c566f3802 | ||
|
|
c9c3c9ab90 | ||
|
|
56394ac50e | ||
|
|
f7c2f12ab2 | ||
|
|
a6bb6e0c93 | ||
|
|
4a5863e6ac |
164
v2/cmd/wails/internal/commands/update/update.go
Normal file
164
v2/cmd/wails/internal/commands/update/update.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/shell"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/github"
|
||||||
|
|
||||||
|
"github.com/leaanthony/clir"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddSubcommand adds the `init` command for the Wails application
|
||||||
|
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
|
||||||
|
|
||||||
|
command := app.NewSubCommand("update", "Update the Wails CLI")
|
||||||
|
command.LongDescription(`This command allows you to update your version of Wails.`)
|
||||||
|
|
||||||
|
// Setup flags
|
||||||
|
var prereleaseRequired bool
|
||||||
|
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
|
||||||
|
|
||||||
|
var specificVersion string
|
||||||
|
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
|
||||||
|
|
||||||
|
command.Action(func() error {
|
||||||
|
|
||||||
|
// Create logger
|
||||||
|
logger := clilogger.New(w)
|
||||||
|
|
||||||
|
// Print banner
|
||||||
|
app.PrintBanner()
|
||||||
|
logger.Println("Checking for updates...")
|
||||||
|
|
||||||
|
var desiredVersion *github.SemanticVersion
|
||||||
|
var err error
|
||||||
|
var valid bool
|
||||||
|
|
||||||
|
if len(specificVersion) > 0 {
|
||||||
|
// Check if this is a valid version
|
||||||
|
valid, err = github.IsValidTag(specificVersion)
|
||||||
|
if err == nil {
|
||||||
|
if !valid {
|
||||||
|
err = fmt.Errorf("version '%s' is invalid", specificVersion)
|
||||||
|
} else {
|
||||||
|
desiredVersion, err = github.NewSemanticVersion(specificVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if prereleaseRequired {
|
||||||
|
desiredVersion, err = github.GetLatestPreRelease()
|
||||||
|
} else {
|
||||||
|
desiredVersion, err = github.GetLatestStableRelease()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println(" Current Version : " + currentVersion)
|
||||||
|
|
||||||
|
if len(specificVersion) > 0 {
|
||||||
|
fmt.Printf(" Desired Version : v%s\n", desiredVersion)
|
||||||
|
} else {
|
||||||
|
if prereleaseRequired {
|
||||||
|
fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Latest Release : v%s\n", desiredVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateToVersion(logger, desiredVersion, len(specificVersion) > 0, currentVersion)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.SemanticVersion, force bool, currentVersion string) error {
|
||||||
|
|
||||||
|
var targetVersionString = "v" + targetVersion.String()
|
||||||
|
|
||||||
|
// Early exit
|
||||||
|
if targetVersionString == currentVersion {
|
||||||
|
logger.Println("Looks like you're up to date!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var desiredVersion string
|
||||||
|
|
||||||
|
if !force {
|
||||||
|
|
||||||
|
compareVersion := currentVersion
|
||||||
|
|
||||||
|
currentVersion, err := github.NewSemanticVersion(compareVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var success bool
|
||||||
|
|
||||||
|
// Release -> Pre-Release = Massage current version to prerelease format
|
||||||
|
if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
|
||||||
|
testVersion, err := github.NewSemanticVersion(compareVersion + "-0")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
success, _ = targetVersion.IsGreaterThan(testVersion)
|
||||||
|
}
|
||||||
|
// Pre-Release -> Release = Massage target version to prerelease format
|
||||||
|
if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
|
||||||
|
// We are ok with greater than or equal
|
||||||
|
mainversion := currentVersion.MainVersion()
|
||||||
|
targetVersion, err = github.NewSemanticVersion(targetVersion.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release -> Release = Standard check
|
||||||
|
if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
|
||||||
|
(targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
|
||||||
|
|
||||||
|
success, _ = targetVersion.IsGreaterThan(currentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
if !success {
|
||||||
|
logger.Println("Error: The requested version is lower than the current version.")
|
||||||
|
logger.Println("If this is what you really want to do, use `wails update -version %s`", targetVersionString)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
desiredVersion = "v" + targetVersion.String()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
desiredVersion = "v" + targetVersion.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
logger.Print("Installing Wails " + desiredVersion + "...")
|
||||||
|
|
||||||
|
// Run command in non module directory
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Cannot find home directory! Please file a bug report!")
|
||||||
|
}
|
||||||
|
|
||||||
|
sout, serr, err := shell.RunCommand(homeDir, "go", "get", "github.com/wailsapp/wails/v2/cmd/wails@"+desiredVersion)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Failed.")
|
||||||
|
logger.Println(sout + `\n` + serr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
logger.Println("Wails updated to " + desiredVersion)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
"github.com/leaanthony/clir"
|
||||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
|
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
|
||||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
|
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
|
||||||
@@ -48,6 +50,11 @@ func main() {
|
|||||||
fatal(err.Error())
|
fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = update.AddSubcommand(app, os.Stdout, version)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
err = app.Run()
|
err = app.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("\n\nERROR: " + err.Error())
|
println("\n\nERROR: " + err.Error())
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var version = "v2.0.0-alpha.16"
|
var version = "v2.0.0-alpha.19"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module github.com/wailsapp/wails/v2
|
|||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Masterminds/semver v1.5.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/fatih/structtag v1.2.0
|
github.com/fatih/structtag v1.2.0
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|||||||
@@ -81,12 +81,15 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
|||||||
|
|
||||||
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
|
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
|
||||||
|
|
||||||
|
// Create binding exemptions - Ugly hack. There must be a better way
|
||||||
|
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
|
||||||
|
|
||||||
result := &App{
|
result := &App{
|
||||||
appType: "desktop",
|
appType: "desktop",
|
||||||
window: window,
|
window: window,
|
||||||
servicebus: servicebus.New(myLogger),
|
servicebus: servicebus.New(myLogger),
|
||||||
logger: myLogger,
|
logger: myLogger,
|
||||||
bindings: binding.NewBindings(myLogger, appoptions.Bind),
|
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
|
||||||
menuManager: menuManager,
|
menuManager: menuManager,
|
||||||
startupCallback: appoptions.Startup,
|
startupCallback: appoptions.Startup,
|
||||||
shutdownCallback: appoptions.Shutdown,
|
shutdownCallback: appoptions.Shutdown,
|
||||||
|
|||||||
@@ -2,23 +2,38 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/leaanthony/slicer"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bindings struct {
|
type Bindings struct {
|
||||||
db *DB
|
db *DB
|
||||||
logger logger.CustomLogger
|
logger logger.CustomLogger
|
||||||
|
exemptions slicer.StringSlicer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBindings returns a new Bindings object
|
// NewBindings returns a new Bindings object
|
||||||
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}) *Bindings {
|
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
|
||||||
result := &Bindings{
|
result := &Bindings{
|
||||||
db: newDB(),
|
db: newDB(),
|
||||||
logger: logger.CustomLogger("Bindings"),
|
logger: logger.CustomLogger("Bindings"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, exemption := range exemptions {
|
||||||
|
if exemptions == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
|
||||||
|
// Yuk yuk yuk! Is there a better way?
|
||||||
|
name = strings.TrimSuffix(name, "-fm")
|
||||||
|
result.exemptions.Add(name)
|
||||||
|
}
|
||||||
|
|
||||||
// Add the structs to bind
|
// Add the structs to bind
|
||||||
for _, ptr := range structPointersToBind {
|
for _, ptr := range structPointersToBind {
|
||||||
err := result.Add(ptr)
|
err := result.Add(ptr)
|
||||||
@@ -33,7 +48,7 @@ func NewBindings(logger *logger.Logger, structPointersToBind []interface{}) *Bin
|
|||||||
// Add the given struct methods to the Bindings
|
// Add the given struct methods to the Bindings
|
||||||
func (b *Bindings) Add(structPtr interface{}) error {
|
func (b *Bindings) Add(structPtr interface{}) error {
|
||||||
|
|
||||||
methods, err := getMethods(structPtr)
|
methods, err := b.getMethods(structPtr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot bind value to app: %s", err.Error())
|
return fmt.Errorf("cannot bind value to app: %s", err.Error())
|
||||||
}
|
}
|
||||||
@@ -46,7 +61,6 @@ func (b *Bindings) Add(structPtr interface{}) error {
|
|||||||
|
|
||||||
// Add it as a regular method
|
// Add it as a regular method
|
||||||
b.db.AddMethod(packageName, structName, methodName, method)
|
b.db.AddMethod(packageName, structName, methodName, method)
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ func (b *BoundMethod) OutputCount() int {
|
|||||||
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
|
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
|
||||||
|
|
||||||
result := make([]interface{}, b.InputCount())
|
result := make([]interface{}, b.InputCount())
|
||||||
|
if len(args) != b.InputCount() {
|
||||||
|
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
|
||||||
|
}
|
||||||
for index, arg := range args {
|
for index, arg := range args {
|
||||||
typ := b.Inputs[index].reflectType
|
typ := b.Inputs[index].reflectType
|
||||||
inputValue := reflect.New(typ).Interface()
|
inputValue := reflect.New(typ).Interface()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func isStruct(value interface{}) bool {
|
|||||||
return reflect.ValueOf(value).Kind() == reflect.Struct
|
return reflect.ValueOf(value).Kind() == reflect.Struct
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMethods(value interface{}) ([]*BoundMethod, error) {
|
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||||
|
|
||||||
// Create result placeholder
|
// Create result placeholder
|
||||||
var result []*BoundMethod
|
var result []*BoundMethod
|
||||||
@@ -56,6 +56,11 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
|
|||||||
fullMethodName := baseName + "." + methodName
|
fullMethodName := baseName + "." + methodName
|
||||||
method := structValue.MethodByName(methodName)
|
method := structValue.MethodByName(methodName)
|
||||||
|
|
||||||
|
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
|
||||||
|
if b.exemptions.Contains(methodReflectName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Create new method
|
// Create new method
|
||||||
boundMethod := &BoundMethod{
|
boundMethod := &BoundMethod{
|
||||||
Name: fullMethodName,
|
Name: fullMethodName,
|
||||||
|
|||||||
21
v2/internal/deepcopy/LICENSE
Normal file
21
v2/internal/deepcopy/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Joel
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
125
v2/internal/deepcopy/deepcopy.go
Normal file
125
v2/internal/deepcopy/deepcopy.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// deepcopy makes deep copies of things. A standard copy will copy the
|
||||||
|
// pointers: deep copy copies the values pointed to. Unexported field
|
||||||
|
// values are not copied.
|
||||||
|
//
|
||||||
|
// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved.
|
||||||
|
// License: MIT, for more details check the included LICENSE file.
|
||||||
|
package deepcopy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface for delegating copy process to type
|
||||||
|
type Interface interface {
|
||||||
|
DeepCopy() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
|
||||||
|
func Iface(iface interface{}) interface{} {
|
||||||
|
return Copy(iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates a deep copy of whatever is passed to it and returns the copy
|
||||||
|
// in an interface{}. The returned value will need to be asserted to the
|
||||||
|
// correct type.
|
||||||
|
func Copy(src interface{}) interface{} {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the interface a reflect.Value
|
||||||
|
original := reflect.ValueOf(src)
|
||||||
|
|
||||||
|
// Make a copy of the same type as the original.
|
||||||
|
cpy := reflect.New(original.Type()).Elem()
|
||||||
|
|
||||||
|
// Recursively copy the original.
|
||||||
|
copyRecursive(original, cpy)
|
||||||
|
|
||||||
|
// Return the copy as an interface.
|
||||||
|
return cpy.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyRecursive does the actual copying of the interface. It currently has
|
||||||
|
// limited support for what it can handle. Add as needed.
|
||||||
|
func copyRecursive(original, cpy reflect.Value) {
|
||||||
|
// check for implement deepcopy.Interface
|
||||||
|
if original.CanInterface() {
|
||||||
|
if copier, ok := original.Interface().(Interface); ok {
|
||||||
|
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle according to original's Kind
|
||||||
|
switch original.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Get the actual value being pointed to.
|
||||||
|
originalValue := original.Elem()
|
||||||
|
|
||||||
|
// if it isn't valid, return.
|
||||||
|
if !originalValue.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cpy.Set(reflect.New(originalValue.Type()))
|
||||||
|
copyRecursive(originalValue, cpy.Elem())
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// If this is a nil, don't do anything
|
||||||
|
if original.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get the value for the interface, not the pointer.
|
||||||
|
originalValue := original.Elem()
|
||||||
|
|
||||||
|
// Get the value by calling Elem().
|
||||||
|
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||||
|
copyRecursive(originalValue, copyValue)
|
||||||
|
cpy.Set(copyValue)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
t, ok := original.Interface().(time.Time)
|
||||||
|
if ok {
|
||||||
|
cpy.Set(reflect.ValueOf(t))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Go through each field of the struct and copy it.
|
||||||
|
for i := 0; i < original.NumField(); i++ {
|
||||||
|
// The Type's StructField for a given field is checked to see if StructField.PkgPath
|
||||||
|
// is set to determine if the field is exported or not because CanSet() returns false
|
||||||
|
// for settable fields. I'm not sure why. -mohae
|
||||||
|
if original.Type().Field(i).PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
copyRecursive(original.Field(i), cpy.Field(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if original.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Make a new slice and copy each element.
|
||||||
|
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
|
||||||
|
for i := 0; i < original.Len(); i++ {
|
||||||
|
copyRecursive(original.Index(i), cpy.Index(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
if original.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cpy.Set(reflect.MakeMap(original.Type()))
|
||||||
|
for _, key := range original.MapKeys() {
|
||||||
|
originalValue := original.MapIndex(key)
|
||||||
|
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||||
|
copyRecursive(originalValue, copyValue)
|
||||||
|
copyKey := Copy(key.Interface())
|
||||||
|
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
cpy.Set(original)
|
||||||
|
}
|
||||||
|
}
|
||||||
1110
v2/internal/deepcopy/deepcopy_test.go
Normal file
1110
v2/internal/deepcopy/deepcopy_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,10 @@ package ffenestri
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,6 +114,14 @@ func (c *Client) WindowSize(width int, height int) {
|
|||||||
C.SetSize(c.app.app, C.int(width), C.int(height))
|
C.SetSize(c.app.app, C.int(width), C.int(height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) WindowSetMinSize(width int, height int) {
|
||||||
|
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) WindowSetMaxSize(width int, height int) {
|
||||||
|
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
|
||||||
|
}
|
||||||
|
|
||||||
// WindowSetColour sets the window colour
|
// WindowSetColour sets the window colour
|
||||||
func (c *Client) WindowSetColour(colour int) {
|
func (c *Client) WindowSetColour(colour int) {
|
||||||
r, g, b, a := intToColour(colour)
|
r, g, b, a := intToColour(colour)
|
||||||
|
|||||||
@@ -140,6 +140,16 @@ void Debug(struct Application *app, const char *message, ... ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Error(struct Application *app, const char *message, ... ) {
|
||||||
|
const char *temp = concat("LEFfenestri (C) | ", message);
|
||||||
|
va_list args;
|
||||||
|
va_start(args, message);
|
||||||
|
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||||
|
app->sendMessageToBackend(&logbuffer[0]);
|
||||||
|
MEMFREE(temp);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
void Fatal(struct Application *app, const char *message, ... ) {
|
void Fatal(struct Application *app, const char *message, ... ) {
|
||||||
const char *temp = concat("LFFfenestri (C) | ", message);
|
const char *temp = concat("LFFfenestri (C) | ", message);
|
||||||
va_list args;
|
va_list args;
|
||||||
@@ -246,7 +256,10 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
struct Application *app = (struct Application *)objc_getAssociatedObject(
|
struct Application *app = (struct Application *)objc_getAssociatedObject(
|
||||||
self, "application");
|
self, "application");
|
||||||
const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String"));
|
const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String"));
|
||||||
if( strcmp(name, "completed") == 0) {
|
if( strcmp(name, "error") == 0 ) {
|
||||||
|
printf("There was a Javascript error. Please open the devtools for more information.\n");
|
||||||
|
Show(app);
|
||||||
|
} else if( strcmp(name, "completed") == 0) {
|
||||||
// Delete handler
|
// Delete handler
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
|
||||||
|
|
||||||
@@ -450,6 +463,7 @@ void DestroyApplication(struct Application *app) {
|
|||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
|
||||||
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error"));
|
||||||
|
|
||||||
// Close main window
|
// Close main window
|
||||||
msg(app->mainWindow, s("close"));
|
msg(app->mainWindow, s("close"));
|
||||||
@@ -885,6 +899,20 @@ void setMinMaxSize(struct Application *app)
|
|||||||
{
|
{
|
||||||
msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight));
|
msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate if window needs resizing
|
||||||
|
int newWidth = app->width;
|
||||||
|
int newHeight = app->height;
|
||||||
|
|
||||||
|
if (app->maxWidth > 0 && app->width > app->maxWidth) newWidth = app->maxWidth;
|
||||||
|
if (app->minWidth > 0 && app->width < app->minWidth) newWidth = app->minWidth;
|
||||||
|
if (app->maxHeight > 0 && app->height > app->maxHeight ) newHeight = app->maxHeight;
|
||||||
|
if (app->minHeight > 0 && app->height < app->minHeight ) newHeight = app->minHeight;
|
||||||
|
|
||||||
|
// If we have any change, resize window
|
||||||
|
if ( newWidth != app->width || newHeight != app->height ) {
|
||||||
|
SetSize(app, newWidth, newHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
||||||
@@ -1534,6 +1562,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
id manager = msg(config, s("userContentController"));
|
id manager = msg(config, s("userContentController"));
|
||||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external"));
|
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external"));
|
||||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed"));
|
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed"));
|
||||||
|
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("error"));
|
||||||
app->manager = manager;
|
app->manager = manager;
|
||||||
|
|
||||||
id wkwebview = msg(c("WKWebView"), s("alloc"));
|
id wkwebview = msg(c("WKWebView"), s("alloc"));
|
||||||
|
|||||||
@@ -740,7 +740,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
const char *image = getJSONString(item, "Image");
|
const char *image = getJSONString(item, "Image");
|
||||||
const char *fontName = getJSONString(item, "FontName");
|
const char *fontName = getJSONString(item, "FontName");
|
||||||
const char *RGBA = getJSONString(item, "RGBA");
|
const char *RGBA = getJSONString(item, "RGBA");
|
||||||
int fontSize = 0;
|
int fontSize = 12;
|
||||||
getJSONInt(item, "FontSize", &fontSize);
|
getJSONInt(item, "FontSize", &fontSize);
|
||||||
|
|
||||||
// If we have an accelerator
|
// If we have an accelerator
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
103
v2/internal/github/github.go
Normal file
103
v2/internal/github/github.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetVersionTags gets the list of tags on the Wails repo
|
||||||
|
// It returns a list of sorted tags in descending order
|
||||||
|
func GetVersionTags() ([]*SemanticVersion, error) {
|
||||||
|
|
||||||
|
result := []*SemanticVersion{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/tags")
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []map[string]interface{}{}
|
||||||
|
err = json.Unmarshal(body, &data)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert tag data to Version structs
|
||||||
|
for _, tag := range data {
|
||||||
|
version := tag["name"].(string)
|
||||||
|
if !strings.HasPrefix(version, "v2") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
semver, err := NewSemanticVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
result = append(result, semver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse Sort
|
||||||
|
sort.Sort(sort.Reverse(SemverCollection(result)))
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestStableRelease gets the latest stable release on GitHub
|
||||||
|
func GetLatestStableRelease() (result *SemanticVersion, err error) {
|
||||||
|
|
||||||
|
tags, err := GetVersionTags()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if tag.IsRelease() {
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no release tag found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestPreRelease gets the latest prerelease on GitHub
|
||||||
|
func GetLatestPreRelease() (result *SemanticVersion, err error) {
|
||||||
|
|
||||||
|
tags, err := GetVersionTags()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if tag.IsPreRelease() {
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no prerelease tag found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidTag returns true if the given string is a valid tag
|
||||||
|
func IsValidTag(tagVersion string) (bool, error) {
|
||||||
|
if tagVersion[0] == 'v' {
|
||||||
|
tagVersion = tagVersion[1:]
|
||||||
|
}
|
||||||
|
tags, err := GetVersionTags()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if tag.String() == tagVersion {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
106
v2/internal/github/semver.go
Normal file
106
v2/internal/github/semver.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SemanticVersion is a struct containing a semantic version
|
||||||
|
type SemanticVersion struct {
|
||||||
|
Version *semver.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSemanticVersion creates a new SemanticVersion object with the given version string
|
||||||
|
func NewSemanticVersion(version string) (*SemanticVersion, error) {
|
||||||
|
semverVersion, err := semver.NewVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SemanticVersion{
|
||||||
|
Version: semverVersion,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRelease returns true if it's a release version
|
||||||
|
func (s *SemanticVersion) IsRelease() bool {
|
||||||
|
// Limit to v2
|
||||||
|
if s.Version.Major() != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPreRelease returns true if it's a prerelease version
|
||||||
|
func (s *SemanticVersion) IsPreRelease() bool {
|
||||||
|
// Limit to v1
|
||||||
|
if s.Version.Major() != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(s.Version.Prerelease()) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SemanticVersion) String() string {
|
||||||
|
return s.Version.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGreaterThan returns true if this version is greater than the given version
|
||||||
|
func (s *SemanticVersion) IsGreaterThan(version *SemanticVersion) (bool, error) {
|
||||||
|
// Set up new constraint
|
||||||
|
constraint, err := semver.NewConstraint("> " + version.Version.String())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the desired one is greater than the requested on
|
||||||
|
success, msgs := constraint.Validate(s.Version)
|
||||||
|
if !success {
|
||||||
|
return false, msgs[0]
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGreaterThanOrEqual returns true if this version is greater than or equal the given version
|
||||||
|
func (s *SemanticVersion) IsGreaterThanOrEqual(version *SemanticVersion) (bool, error) {
|
||||||
|
// Set up new constraint
|
||||||
|
constraint, err := semver.NewConstraint(">= " + version.Version.String())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the desired one is greater than the requested on
|
||||||
|
success, msgs := constraint.Validate(s.Version)
|
||||||
|
if !success {
|
||||||
|
return false, msgs[0]
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainVersion returns the main version of any version+prerelease+metadata
|
||||||
|
// EG: MainVersion("1.2.3-pre") => "1.2.3"
|
||||||
|
func (s *SemanticVersion) MainVersion() *SemanticVersion {
|
||||||
|
mainVersion := fmt.Sprintf("%d.%d.%d", s.Version.Major(), s.Version.Minor(), s.Version.Patch())
|
||||||
|
result, _ := NewSemanticVersion(mainVersion)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemverCollection is a collection of SemanticVersion objects
|
||||||
|
type SemverCollection []*SemanticVersion
|
||||||
|
|
||||||
|
// Len returns the length of a collection. The number of Version instances
|
||||||
|
// on the slice.
|
||||||
|
func (c SemverCollection) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less is needed for the sort interface to compare two Version objects on the
|
||||||
|
// slice. If checks if one is less than the other.
|
||||||
|
func (c SemverCollection) Less(i, j int) bool {
|
||||||
|
return c[i].Version.LessThan(c[j].Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap is needed for the sort interface to replace the Version objects
|
||||||
|
// at two different positions in the slice.
|
||||||
|
func (c SemverCollection) Swap(i, j int) {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package messagedispatcher
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
@@ -26,6 +27,8 @@ type Client interface {
|
|||||||
WindowUnminimise()
|
WindowUnminimise()
|
||||||
WindowPosition(x int, y int)
|
WindowPosition(x int, y int)
|
||||||
WindowSize(width int, height int)
|
WindowSize(width int, height int)
|
||||||
|
WindowSetMinSize(width int, height int)
|
||||||
|
WindowSetMaxSize(width int, height int)
|
||||||
WindowFullscreen()
|
WindowFullscreen()
|
||||||
WindowUnFullscreen()
|
WindowUnFullscreen()
|
||||||
WindowSetColour(colour int)
|
WindowSetColour(colour int)
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package messagedispatcher
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/crypto"
|
"github.com/wailsapp/wails/v2/internal/crypto"
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||||
@@ -349,6 +350,38 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
|
|||||||
for _, client := range d.clients {
|
for _, client := range d.clients {
|
||||||
client.frontend.WindowSize(w, h)
|
client.frontend.WindowSize(w, h)
|
||||||
}
|
}
|
||||||
|
case "minsize":
|
||||||
|
// We need 2 arguments
|
||||||
|
if len(splitTopic) != 4 {
|
||||||
|
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w, err1 := strconv.Atoi(splitTopic[2])
|
||||||
|
h, err2 := strconv.Atoi(splitTopic[3])
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Notifh clients
|
||||||
|
for _, client := range d.clients {
|
||||||
|
client.frontend.WindowSetMinSize(w, h)
|
||||||
|
}
|
||||||
|
case "maxsize":
|
||||||
|
// We need 2 arguments
|
||||||
|
if len(splitTopic) != 4 {
|
||||||
|
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w, err1 := strconv.Atoi(splitTopic[2])
|
||||||
|
h, err2 := strconv.Atoi(splitTopic[3])
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Notifh clients
|
||||||
|
for _, client := range d.clients {
|
||||||
|
client.frontend.WindowSetMaxSize(w, h)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
d.logger.Error("Unknown window command: %s", command)
|
d.logger.Error("Unknown window command: %s", command)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -10,6 +10,7 @@ The lightweight framework for web-like apps
|
|||||||
/* jshint esversion: 6 */
|
/* jshint esversion: 6 */
|
||||||
import { SetBindings } from './bindings';
|
import { SetBindings } from './bindings';
|
||||||
import { Init } from './main';
|
import { Init } from './main';
|
||||||
|
import {RaiseError} from '../desktop/darwin';
|
||||||
|
|
||||||
// Setup global error handler
|
// Setup global error handler
|
||||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||||
@@ -21,7 +22,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|||||||
error: JSON.stringify(error),
|
error: JSON.stringify(error),
|
||||||
stack: function() { return JSON.stringify(new Error().stack); }(),
|
stack: function() { return JSON.stringify(new Error().stack); }(),
|
||||||
};
|
};
|
||||||
window.wails.Log.Error(JSON.stringify(errorMessage));
|
RaiseError(errorMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialise the Runtime
|
// Initialise the Runtime
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export function SendMessage(message) {
|
|||||||
window.webkit.messageHandlers.external.postMessage(message);
|
window.webkit.messageHandlers.external.postMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RaiseError(message) {
|
||||||
|
window.webkit.messageHandlers.error.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
export function Init() {
|
export function Init() {
|
||||||
|
|
||||||
// Setup drag handler
|
// Setup drag handler
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ module.exports = {
|
|||||||
mode: 'production',
|
mode: 'production',
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, '..', 'assets'),
|
path: path.resolve(__dirname, '..', 'assets'),
|
||||||
filename: 'desktop.js',
|
filename: 'desktop_'+platform+'.js',
|
||||||
library: 'Wails'
|
library: 'Wails'
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/wailsapp/wails/v2/internal/fs"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/shell"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/fs"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -49,7 +50,7 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop.js")
|
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop_" + platform + ".js")
|
||||||
runtimeData, err := ioutil.ReadFile(wailsJS)
|
runtimeData, err := ioutil.ReadFile(wailsJS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
golog "log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/deepcopy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options defines the optional data that may be used
|
// Options defines the optional data that may be used
|
||||||
@@ -64,21 +67,31 @@ func fatal(err error) {
|
|||||||
// New creates a new store
|
// New creates a new store
|
||||||
func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
|
func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
|
||||||
|
|
||||||
dataType := reflect.TypeOf(defaultValue)
|
if defaultValue == nil {
|
||||||
|
golog.Fatal("Cannot initialise a store with nil")
|
||||||
|
}
|
||||||
|
|
||||||
result := Store{
|
result := Store{
|
||||||
name: name,
|
name: name,
|
||||||
runtime: p.runtime,
|
runtime: p.runtime,
|
||||||
data: reflect.ValueOf(defaultValue),
|
|
||||||
dataType: dataType,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the sync listener
|
// Setup the sync listener
|
||||||
result.setupListener()
|
result.setupListener()
|
||||||
|
|
||||||
|
result.Set(defaultValue)
|
||||||
|
|
||||||
return &result
|
return &result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) lock() {
|
||||||
|
s.mux.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) unlock() {
|
||||||
|
s.mux.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// OnError takes a function that will be called
|
// OnError takes a function that will be called
|
||||||
// whenever an error occurs
|
// whenever an error occurs
|
||||||
func (s *Store) OnError(callback func(error)) {
|
func (s *Store) OnError(callback func(error)) {
|
||||||
@@ -105,7 +118,7 @@ func (s *Store) processUpdatedData(data string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock mutex for writing
|
// Lock mutex for writing
|
||||||
s.mux.Lock()
|
s.lock()
|
||||||
|
|
||||||
// Handle nulls
|
// Handle nulls
|
||||||
if newData == nil {
|
if newData == nil {
|
||||||
@@ -116,7 +129,7 @@ func (s *Store) processUpdatedData(data string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unlock mutex
|
// Unlock mutex
|
||||||
s.mux.Unlock()
|
s.unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -153,17 +166,27 @@ func (s *Store) setupListener() {
|
|||||||
|
|
||||||
func (s *Store) resync() {
|
func (s *Store) resync() {
|
||||||
|
|
||||||
// Stringify data
|
// Lock
|
||||||
newdata, err := json.Marshal(s.data.Interface())
|
s.lock()
|
||||||
if err != nil {
|
defer s.unlock()
|
||||||
if s.errorHandler != nil {
|
|
||||||
s.errorHandler(err)
|
var result string
|
||||||
return
|
|
||||||
|
if s.data.IsValid() {
|
||||||
|
rawdata, err := json.Marshal(s.data.Interface())
|
||||||
|
if err != nil {
|
||||||
|
if s.errorHandler != nil {
|
||||||
|
s.errorHandler(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
result = string(rawdata)
|
||||||
|
} else {
|
||||||
|
result = "{}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit event to front end
|
// Emit event to front end
|
||||||
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, string(newdata))
|
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, result)
|
||||||
|
|
||||||
// Notify subscribers
|
// Notify subscribers
|
||||||
s.notify()
|
s.notify()
|
||||||
@@ -176,7 +199,9 @@ func (s *Store) notify() {
|
|||||||
for _, callback := range s.callbacks {
|
for _, callback := range s.callbacks {
|
||||||
|
|
||||||
// Build args
|
// Build args
|
||||||
|
s.lock()
|
||||||
args := []reflect.Value{s.data}
|
args := []reflect.Value{s.data}
|
||||||
|
s.unlock()
|
||||||
|
|
||||||
if s.notifySynchronously {
|
if s.notifySynchronously {
|
||||||
callback.Call(args)
|
callback.Call(args)
|
||||||
@@ -191,16 +216,31 @@ func (s *Store) notify() {
|
|||||||
// and notify listeners of the change
|
// and notify listeners of the change
|
||||||
func (s *Store) Set(data interface{}) error {
|
func (s *Store) Set(data interface{}) error {
|
||||||
|
|
||||||
inType := reflect.TypeOf(data)
|
if data == nil {
|
||||||
|
return fmt.Errorf("cannot set store to nil")
|
||||||
|
}
|
||||||
|
|
||||||
if inType != s.dataType {
|
s.lock()
|
||||||
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
|
|
||||||
|
dataCopy := deepcopy.Copy(data)
|
||||||
|
|
||||||
|
if dataCopy != nil {
|
||||||
|
inType := reflect.TypeOf(dataCopy)
|
||||||
|
|
||||||
|
if inType != s.dataType && s.data.IsValid() {
|
||||||
|
s.unlock()
|
||||||
|
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.dataType == nil {
|
||||||
|
s.dataType = reflect.TypeOf(dataCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save data
|
// Save data
|
||||||
s.mux.Lock()
|
s.data = reflect.ValueOf(dataCopy)
|
||||||
s.data = reflect.ValueOf(data)
|
|
||||||
s.mux.Unlock()
|
s.unlock()
|
||||||
|
|
||||||
// Resync with subscribers
|
// Resync with subscribers
|
||||||
s.resync()
|
s.resync()
|
||||||
@@ -251,7 +291,9 @@ func (s *Store) Subscribe(callback interface{}) {
|
|||||||
|
|
||||||
callbackFunc := reflect.ValueOf(callback)
|
callbackFunc := reflect.ValueOf(callback)
|
||||||
|
|
||||||
|
s.lock()
|
||||||
s.callbacks = append(s.callbacks, callbackFunc)
|
s.callbacks = append(s.callbacks, callbackFunc)
|
||||||
|
s.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// updaterCheck ensures the given function to Update() is
|
// updaterCheck ensures the given function to Update() is
|
||||||
@@ -301,7 +343,9 @@ func (s *Store) Update(updater interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build args
|
// Build args
|
||||||
|
s.lock()
|
||||||
args := []reflect.Value{s.data}
|
args := []reflect.Value{s.data}
|
||||||
|
s.unlock()
|
||||||
|
|
||||||
// Make call
|
// Make call
|
||||||
results := reflect.ValueOf(updater).Call(args)
|
results := reflect.ValueOf(updater).Call(args)
|
||||||
@@ -312,5 +356,12 @@ func (s *Store) Update(updater interface{}) {
|
|||||||
|
|
||||||
// Get returns the value of the data that's kept in the current state / Store
|
// Get returns the value of the data that's kept in the current state / Store
|
||||||
func (s *Store) Get() interface{} {
|
func (s *Store) Get() interface{} {
|
||||||
|
s.lock()
|
||||||
|
defer s.unlock()
|
||||||
|
|
||||||
|
if !s.data.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return s.data.Interface()
|
return s.data.Interface()
|
||||||
}
|
}
|
||||||
|
|||||||
165
v2/internal/runtime/store_test.go
Normal file
165
v2/internal/runtime/store_test.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
internallogger "github.com/wailsapp/wails/v2/internal/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
|
|
||||||
|
is2 "github.com/matryer/is"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStoreProvider_NewWithNilDefault(t *testing.T) {
|
||||||
|
is := is2.New(t)
|
||||||
|
|
||||||
|
defaultLogger := logger.NewDefaultLogger()
|
||||||
|
testLogger := internallogger.New(defaultLogger)
|
||||||
|
//testLogger.SetLogLevel(logger.TRACE)
|
||||||
|
serviceBus := servicebus.New(testLogger)
|
||||||
|
err := serviceBus.Start()
|
||||||
|
is.NoErr(err)
|
||||||
|
defer serviceBus.Stop()
|
||||||
|
|
||||||
|
testRuntime := New(serviceBus)
|
||||||
|
storeProvider := newStore(testRuntime)
|
||||||
|
|
||||||
|
testStore := storeProvider.New("test", 0)
|
||||||
|
|
||||||
|
// You should be able to write a new value into a
|
||||||
|
// store initialised with nil
|
||||||
|
err = testStore.Set(100)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
// You shouldn't be able to write different types to the
|
||||||
|
// store
|
||||||
|
err = testStore.Set(false)
|
||||||
|
is.True(err != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreProvider_NewWithScalarDefault(t *testing.T) {
|
||||||
|
is := is2.New(t)
|
||||||
|
|
||||||
|
defaultLogger := logger.NewDefaultLogger()
|
||||||
|
testLogger := internallogger.New(defaultLogger)
|
||||||
|
//testLogger.SetLogLevel(logger.TRACE)
|
||||||
|
serviceBus := servicebus.New(testLogger)
|
||||||
|
err := serviceBus.Start()
|
||||||
|
is.NoErr(err)
|
||||||
|
defer serviceBus.Stop()
|
||||||
|
|
||||||
|
testRuntime := New(serviceBus)
|
||||||
|
storeProvider := newStore(testRuntime)
|
||||||
|
testStore := storeProvider.New("test", 100)
|
||||||
|
value := testStore.Get()
|
||||||
|
is.Equal(value, 100)
|
||||||
|
testStore.resync()
|
||||||
|
value = testStore.Get()
|
||||||
|
is.Equal(value, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreProvider_NewWithStructDefault(t *testing.T) {
|
||||||
|
is := is2.New(t)
|
||||||
|
|
||||||
|
defaultLogger := logger.NewDefaultLogger()
|
||||||
|
testLogger := internallogger.New(defaultLogger)
|
||||||
|
//testLogger.SetLogLevel(logger.TRACE)
|
||||||
|
serviceBus := servicebus.New(testLogger)
|
||||||
|
err := serviceBus.Start()
|
||||||
|
is.NoErr(err)
|
||||||
|
defer serviceBus.Stop()
|
||||||
|
|
||||||
|
testRuntime := New(serviceBus)
|
||||||
|
storeProvider := newStore(testRuntime)
|
||||||
|
|
||||||
|
type TestValue struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
testValue := &TestValue{
|
||||||
|
Name: "hi",
|
||||||
|
}
|
||||||
|
|
||||||
|
testStore := storeProvider.New("test", testValue)
|
||||||
|
|
||||||
|
err = testStore.Set(testValue)
|
||||||
|
is.NoErr(err)
|
||||||
|
testStore.resync()
|
||||||
|
value := testStore.Get()
|
||||||
|
is.Equal(value, testValue)
|
||||||
|
is.Equal(value.(*TestValue).Name, "hi")
|
||||||
|
|
||||||
|
testValue = &TestValue{
|
||||||
|
Name: "there",
|
||||||
|
}
|
||||||
|
err = testStore.Set(testValue)
|
||||||
|
is.NoErr(err)
|
||||||
|
testStore.resync()
|
||||||
|
value = testStore.Get()
|
||||||
|
is.Equal(value, testValue)
|
||||||
|
is.Equal(value.(*TestValue).Name, "there")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreProvider_RapidReadWrite(t *testing.T) {
|
||||||
|
is := is2.New(t)
|
||||||
|
|
||||||
|
defaultLogger := logger.NewDefaultLogger()
|
||||||
|
testLogger := internallogger.New(defaultLogger)
|
||||||
|
//testLogger.SetLogLevel(logger.TRACE)
|
||||||
|
serviceBus := servicebus.New(testLogger)
|
||||||
|
err := serviceBus.Start()
|
||||||
|
is.NoErr(err)
|
||||||
|
defer serviceBus.Stop()
|
||||||
|
|
||||||
|
testRuntime := New(serviceBus)
|
||||||
|
storeProvider := newStore(testRuntime)
|
||||||
|
|
||||||
|
testStore := storeProvider.New("test", 1)
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
readers := 100
|
||||||
|
writers := 100
|
||||||
|
wg.Add(readers + writers)
|
||||||
|
// Setup readers
|
||||||
|
go func(testStore *Store, ctx context.Context) {
|
||||||
|
for readerCount := 0; readerCount < readers; readerCount++ {
|
||||||
|
go func(store *Store, ctx context.Context, id int) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
store.Get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(testStore, ctx, readerCount)
|
||||||
|
}
|
||||||
|
}(testStore, ctx)
|
||||||
|
|
||||||
|
// Setup writers
|
||||||
|
go func(testStore *Store, ctx context.Context) {
|
||||||
|
for writerCount := 0; writerCount < writers; writerCount++ {
|
||||||
|
go func(store *Store, ctx context.Context, id int) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
err := store.Set(rand.Int())
|
||||||
|
is.NoErr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(testStore, ctx, writerCount)
|
||||||
|
}
|
||||||
|
}(testStore, ctx)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ type Window interface {
|
|||||||
Unminimise()
|
Unminimise()
|
||||||
SetTitle(title string)
|
SetTitle(title string)
|
||||||
SetSize(width int, height int)
|
SetSize(width int, height int)
|
||||||
|
SetMinSize(width int, height int)
|
||||||
|
SetMaxSize(width int, height int)
|
||||||
SetPosition(x int, y int)
|
SetPosition(x int, y int)
|
||||||
Fullscreen()
|
Fullscreen()
|
||||||
UnFullscreen()
|
UnFullscreen()
|
||||||
@@ -85,6 +87,18 @@ func (w *window) SetSize(width int, height int) {
|
|||||||
w.bus.Publish(message, "")
|
w.bus.Publish(message, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSize sets the size of the window
|
||||||
|
func (w *window) SetMinSize(width int, height int) {
|
||||||
|
message := fmt.Sprintf("window:minsize:%d:%d", width, height)
|
||||||
|
w.bus.Publish(message, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSize sets the size of the window
|
||||||
|
func (w *window) SetMaxSize(width int, height int) {
|
||||||
|
message := fmt.Sprintf("window:maxsize:%d:%d", width, height)
|
||||||
|
w.bus.Publish(message, "")
|
||||||
|
}
|
||||||
|
|
||||||
// SetPosition sets the position of the window
|
// SetPosition sets the position of the window
|
||||||
func (w *window) SetPosition(x int, y int) {
|
func (w *window) SetPosition(x int, y int) {
|
||||||
message := fmt.Sprintf("window:position:%d:%d", x, y)
|
message := fmt.Sprintf("window:position:%d:%d", x, y)
|
||||||
|
|||||||
@@ -71,10 +71,11 @@ func (s *ServiceBus) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We run in a different thread
|
// We run in a different thread
|
||||||
|
s.wg.Add(1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|
||||||
quit := false
|
quit := false
|
||||||
s.wg.Add(1)
|
|
||||||
|
|
||||||
// Loop until we get a quit message
|
// Loop until we get a quit message
|
||||||
for !quit {
|
for !quit {
|
||||||
|
|||||||
Reference in New Issue
Block a user