Compare commits

..

17 Commits

Author SHA1 Message Date
Lea Anthony
29715b2d57 [WIP] 2021-02-19 20:37:02 +11:00
Lea Anthony
d8ad250608 Template Image support 2021-02-19 05:25:23 +11:00
Lea Anthony
eac8880f6d Initial support for tray menus 2021-02-13 20:26:21 +11:00
Lea Anthony
f47100e71c Sort generated methods to appease git diff. 2021-02-13 18:04:00 +11:00
Lea Anthony
9a81a57d13 Revert bootstrapping. 2021-02-12 15:08:54 +11:00
Lea Anthony
354429bc28 Support UpdateTrayLabel in dev mode. Small refactor. 2021-02-12 14:52:47 +11:00
Lea Anthony
99d4d9490c Improved dialog support for dev mode 2021-02-12 14:50:58 +11:00
Lea Anthony
61afe711bd v2.0.0-alpha.26 2021-02-11 07:17:28 +11:00
Lea Anthony
6e3cfc157f Add bridge.js 2021-02-11 07:09:40 +11:00
Lea Anthony
30d762372f @wails/runtime v1.3.9 2021-02-11 07:04:01 +11:00
Lea Anthony
2dbaabb74c Keep package.json files 2021-02-11 07:02:57 +11:00
Lea Anthony
f8bae0430f Add windowDrag shim for bridge 2021-02-11 06:52:07 +11:00
Lea Anthony
21c07497d7 Move bridge to Svelte build. Initial support for trays in dev mode 2021-02-10 22:53:14 +11:00
Lea Anthony
9b9bcd657f v2.0.0-alpha.25 2021-02-09 21:21:51 +11:00
Lea Anthony
02038aa543 Bugfix dialogs 2021-02-09 21:21:31 +11:00
Lea Anthony
9910d1127a v2.0.0-alpha.24 2021-02-09 21:11:11 +11:00
Lea Anthony
21a0245985 Generate bindings in package. Support dialogs in dev mode. 2021-02-09 21:10:06 +11:00
38 changed files with 3572 additions and 436 deletions

View File

@@ -1 +1 @@
bridge.js
index.js

View File

@@ -1,6 +1,7 @@
package dev
import (
"context"
"fmt"
"io"
"os"
@@ -10,14 +11,18 @@ import (
"syscall"
"time"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wzshiming/ctc"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddSubcommand adds the `dev` command for the Wails application
@@ -44,212 +49,262 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
app.PrintBanner()
// TODO: Check you are in a project directory
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
var debugBinaryProcess *process.Process = nil
var buildFrontend bool = false
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
debounceQuit := make(chan bool, 1)
// Do initial build
logger.Println("Building application for development...")
debugBinaryProcess, err = restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
if err != nil {
return err
}
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event)
// Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
err := watcher.Add(event.Name)
if err != nil {
logger.Fatal("%s", err.Error())
}
logger.Println("Watching directory: %s", event.Name)
}
}
return
}
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
logger.Println("modified file: %s", event.Name)
var rebuild bool = false
// Iterate all file patterns
for _, pattern := range extensionsThatTriggerARebuild {
rebuild = strings.HasSuffix(event.Name, pattern)
if err != nil {
logger.Fatal(err.Error())
}
if rebuild {
// Only build frontend when the file isn't a Go file
buildFrontend = !strings.HasSuffix(event.Name, "go")
break
}
}
if !rebuild {
logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
return
}
logger.Println("Partial build triggered: %s updated", event.Name)
// Do a rebuild
// Try and build the app
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
}
// If we have a new process, save it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
}
}
})
// Get project dir
dir, err := os.Getwd()
reloader, err := NewReloader(logger, extensionsThatTriggerARebuild, ldflags, compilerCommand)
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(dir)
// Start
err = reloader.Start()
if err != nil {
return err
println("ERRRRRRRRRR: %+v", err)
}
// Setup a watcher for non-node_modules directories
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
logger.Println("Watching directory: %s", dir)
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
}
})
logger.Println("\nDevelopment mode exited")
// Wait until we get a quit signal
quit := false
for quit == false {
select {
case <-quitChannel:
println("Caught quit")
// Notify debouncer to quit
debounceQuit <- true
quit = true
}
}
// Kill the current program if running
if debugBinaryProcess != nil {
err := debugBinaryProcess.Kill()
if err != nil {
return err
}
}
logger.Println("Development mode exited")
return nil
return err
})
return nil
}
// Credit: https://drailing.net/2018/01/debounce-function-for-golang/
func debounce(interval time.Duration, input chan fsnotify.Event, quitChannel chan bool, cb func(arg fsnotify.Event)) {
var item fsnotify.Event
timer := time.NewTimer(interval)
exit:
type Reloader struct {
// Main context
ctx context.Context
// Signal context
signalContext context.Context
// notify
watcher *fsnotify.Watcher
// Logger
logger *clilogger.CLILogger
// Extensions to listen for
extensionsThatTriggerARebuild []string
// The binary we are running
binary *process.Process
// options
ldflags string
compiler string
}
func NewReloader(logger *clilogger.CLILogger, extensionsThatTriggerARebuild []string, ldFlags string, compiler string) (*Reloader, error) {
var result Reloader
// Create context
result.ctx = context.Background()
// Signal context (we don't need cancel)
signalContext, _ := signal.NotifyContext(result.ctx, os.Interrupt, os.Kill, syscall.SIGTERM)
result.signalContext = signalContext
// Create watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
result.watcher = watcher
// Logger
result.logger = logger
// Extensions
result.extensionsThatTriggerARebuild = extensionsThatTriggerARebuild
// Options
result.ldflags = ldFlags
result.compiler = compiler
return &result, nil
}
func (r *Reloader) Start() error {
err := r.rebuildBinary()
if err != nil {
return err
}
// Get project dir
dir, err := os.Getwd()
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(dir)
if err != nil {
return err
}
// Setup a watcher for non-node_modules directories
r.logger.Println("Watching (sub)directories: %s", dir)
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
err = r.watcher.Add(dir)
if err != nil {
r.logger.Fatal(err.Error())
}
})
// Main loop
for {
select {
case item = <-input:
timer.Reset(interval)
case <-timer.C:
if item.Name != "" {
cb(item)
case <-r.signalContext.Done():
if r.binary != nil {
println("Binary is not nil - kill")
return r.binary.Kill()
}
return nil
case event := <-r.watcher.Events:
err := r.processWatcherEvent(event)
if err != nil {
println("error from processWatcherEvent. Calling cancel()")
println("Calling kill")
return r.binary.Kill()
}
case <-quitChannel:
break exit
}
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) {
func (r *Reloader) processWatcherEvent(event fsnotify.Event) error {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
println()
if err != nil {
logger.Fatal(err.Error())
return nil, errors.Wrap(err, "Build Failed:")
// Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
err := r.watcher.Add(event.Name)
if err != nil {
return err
}
r.logger.Println("Watching directory: %s", event.Name)
}
}
return nil
}
logger.Println("Build new binary: %s", appBinary)
// Kill existing binary if need be
if debugBinaryProcess != nil {
killError := debugBinaryProcess.Kill()
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
if killError != nil {
logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
var rebuild bool
// Iterate all file patterns
for _, pattern := range r.extensionsThatTriggerARebuild {
if strings.HasSuffix(event.Name, pattern) {
rebuild = true
}
}
debugBinaryProcess = nil
if !rebuild {
return nil
}
r.logger.Println("\n%s[Build triggered] %s %s", ctc.ForegroundGreen|ctc.ForegroundBright, event.Name, ctc.Reset)
return r.rebuildBinary()
}
return nil
}
func (r *Reloader) rebuildBinary() error {
// rebuild binary
binary, err := r.buildApp()
if err != nil {
return err
}
// TODO: Generate `backend.js`
// Kill current binary if running
if r.binary != nil {
err = r.binary.Kill()
if err != nil {
return err
}
}
// Start up new binary
newProcess := process.NewProcess(logger, appBinary)
newProcess := process.NewProcess(r.ctx, r.logger, binary)
err = newProcess.Start()
if err != nil {
// Remove binary
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
logger.Fatal("Unable to delete app binary: " + appBinary)
}
logger.Fatal("Unable to start application: %s", err.Error())
return err
}
return newProcess, nil
// Ensure process runs correctly
return nil
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
//func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) {
//
// appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
// println()
// if err != nil {
// logger.Fatal(err.Error())
// return nil, errors.Wrap(err, "Build Failed:")
// }
// logger.Println("Build new binary: %s", appBinary)
//
// // Kill existing binary if need be
// if debugBinaryProcess != nil {
// killError := debugBinaryProcess.Kill()
//
// if killError != nil {
// logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
// }
//
// debugBinaryProcess = nil
// }
//
// // TODO: Generate `backend.js`
//
// // Start up new binary
// newProcess := process.NewProcess(logger, appBinary)
// err = newProcess.Start()
// if err != nil {
// // Remove binary
// deleteError := fs.DeleteFile(appBinary)
// if deleteError != nil {
// logger.Fatal("Unable to delete app binary: " + appBinary)
// }
// logger.Fatal("Unable to start application: %s", err.Error())
// }
//
// // Check if port is open
// timeout := time.Second
// conn, err := net.DialTimeout("tcp", net.JoinHostPort("host", port), timeout)
// if err != nil {
// return
// }
// newProcess.Running
// return newProcess, nil
//}
// buildapp attempts to compile the application
// It returns the path to the new binary or an error
func (r *Reloader) buildApp() (string, error) {
// Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Logger: r.logger,
OutputType: "dev",
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
LDFlags: r.ldflags,
Compiler: r.compiler,
OutputFile: outputFile,
IgnoreFrontend: true,
}

View File

@@ -1,8 +1,11 @@
package main
import (
"fmt"
"os"
"github.com/wzshiming/ctc"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
"github.com/leaanthony/clir"
@@ -19,12 +22,30 @@ func fatal(message string) {
os.Exit(1)
}
func col(colour ctc.Color, text string) string {
return fmt.Sprintf("%s%s%s", colour, text, ctc.Reset)
}
func Yellow(str string) string {
return col(ctc.ForegroundBrightYellow, str)
}
func Red(str string) string {
return col(ctc.ForegroundBrightRed, str)
}
func banner(cli *clir.Cli) string {
return fmt.Sprintf("%s %s - Go/HTML Application Framework", Yellow("Wails"), Red(version))
}
func main() {
var err error
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
app.SetBannerFunction(banner)
build.AddBuildSubcommand(app, os.Stdout)
err = initialise.AddSubcommand(app, os.Stdout)
if err != nil {

View File

@@ -1,3 +1,3 @@
package main
var version = "v2.0.0-alpha.23"
var version = "v2.0.0-alpha.26"

View File

@@ -20,8 +20,10 @@ require (
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/wzshiming/ctc v1.2.3 // indirect
github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
nhooyr.io/websocket v1.8.6

View File

@@ -78,6 +78,10 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -92,9 +96,12 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=

View File

@@ -4,8 +4,9 @@ package app
import (
"flag"
"github.com/wailsapp/wails/v2/pkg/logger"
"strings"
"github.com/wailsapp/wails/v2/pkg/logger"
)
// Init initialises the application for a debug environment

View File

@@ -97,6 +97,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
bridge: bridge.NewBridge(myLogger),
menuManager: menuManager,
}
result.options = appoptions
@@ -208,7 +209,10 @@ func (a *App) Run() error {
return err
}
err = a.bridge.Run(dispatcher, bindingDump, a.debug)
// Generate backend.js
a.bindings.GenerateBackendJS()
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
a.logger.Trace("Bridge.Run() exited")
if err != nil {
return err

View File

@@ -0,0 +1,11 @@
{
"name": "backend",
"version": "1.0.0",
"description": "Package to wrap backend method calls",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

@@ -0,0 +1,170 @@
package binding
import (
"bytes"
_ "embed"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/leaanthony/slicer"
)
const _comment = `
const backend = {
main: {
"xbarApp": {
"GetCategories": () => {
window.backend.main.xbarApp.GetCategories.call(arguments);
},
/**
* @param {string} arg1
*/
"InstallPlugin": (arg1) => {
window.backend.main.xbarApp.InstallPlugin.call(arguments);
},
"GetPlugins": () => {
window.backend.main.xbarApp.GetPlugins.call(arguments);
}
}
}
}
export default backend;`
//go:embed assets/package.json
var packageJSON []byte
func (b *Bindings) GenerateBackendJS() {
store := b.db.store
var output bytes.Buffer
output.WriteString(`// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
const backend = {`)
output.WriteString("\n")
var sortedPackageNames slicer.StringSlicer
for packageName := range store {
sortedPackageNames.Add(packageName)
}
sortedPackageNames.Sort()
for _, packageName := range sortedPackageNames.AsSlice() {
packages := store[packageName]
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
output.WriteString("\n")
var sortedStructNames slicer.StringSlicer
for structName := range packages {
sortedStructNames.Add(structName)
}
sortedStructNames.Sort()
for _, structName := range sortedStructNames.AsSlice() {
structs := packages[structName]
output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
output.WriteString("\n")
var sortedMethodNames slicer.StringSlicer
for methodName := range structs {
sortedMethodNames.Add(methodName)
}
sortedMethodNames.Sort()
for _, methodName := range sortedMethodNames.AsSlice() {
methodDetails := structs[methodName]
output.WriteString(" /**\n")
output.WriteString(" * " + methodName + "\n")
var args slicer.StringSlicer
for count, input := range methodDetails.Inputs {
arg := fmt.Sprintf("arg%d", count+1)
args.Add(arg)
output.WriteString(fmt.Sprintf(" * @param {%s} %s - Go Type: %s\n", goTypeToJSDocType(input.TypeName), arg, input.TypeName))
}
returnType := "Promise"
returnTypeDetails := ""
if methodDetails.OutputCount() > 0 {
firstType := goTypeToJSDocType(methodDetails.Outputs[0].TypeName)
returnType += "<" + firstType
if methodDetails.OutputCount() == 2 {
secondType := goTypeToJSDocType(methodDetails.Outputs[1].TypeName)
returnType += "|" + secondType
}
returnType += ">"
returnTypeDetails = " - Go Type: " + methodDetails.Outputs[0].TypeName
}
output.WriteString(" * @returns {" + returnType + "} " + returnTypeDetails + "\n")
output.WriteString(" */\n")
argsString := args.Join(", ")
output.WriteString(fmt.Sprintf(" \"%s\": (%s) => {", methodName, argsString))
output.WriteString("\n")
output.WriteString(fmt.Sprintf(" return window.backend.%s.%s.%s(%s);", packageName, structName, methodName, argsString))
output.WriteString("\n")
output.WriteString(fmt.Sprintf(" },"))
output.WriteString("\n")
}
output.WriteString(fmt.Sprintf(" }"))
output.WriteString("\n")
}
output.WriteString(fmt.Sprintf(" }\n"))
output.WriteString("\n")
}
output.WriteString(`};
export default backend;`)
output.WriteString("\n")
dirname, err := fs.RelativeToCwd("frontend/src/backend")
if err != nil {
log.Fatal(err)
}
if !fs.DirExists(dirname) {
err := fs.Mkdir(dirname)
if err != nil {
log.Fatal(err)
}
}
packageJsonFile := filepath.Join(dirname, "package.json")
if !fs.FileExists(packageJsonFile) {
err := os.WriteFile(packageJsonFile, packageJSON, 0755)
if err != nil {
log.Fatal(err)
}
}
filename := filepath.Join(dirname, "index.js")
err = os.WriteFile(filename, output.Bytes(), 0755)
if err != nil {
log.Fatal(err)
}
}
func goTypeToJSDocType(input string) string {
switch true {
case input == "string":
return "string"
case input == "error":
return "Error"
case
strings.HasPrefix(input, "int"),
strings.HasPrefix(input, "uint"),
strings.HasPrefix(input, "float"):
return "number"
case input == "bool":
return "boolean"
case strings.HasPrefix(input, "[]"):
arrayType := goTypeToJSDocType(input[2:])
return "Array.<" + arrayType + ">"
default:
return "any"
}
}

View File

@@ -6,6 +6,8 @@ import (
"net/http"
"sync"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/gorilla/websocket"
@@ -25,6 +27,12 @@ type Bridge struct {
ctx context.Context
cancel context.CancelFunc
// Dialog client
dialog *messagedispatcher.DispatchClient
// Menus
menumanager *menumanager.Manager
}
func NewBridge(myLogger *logger.Logger) *Bridge {
@@ -44,13 +52,19 @@ func NewBridge(myLogger *logger.Logger) *Bridge {
return result
}
func (b *Bridge) Run(dispatcher *messagedispatcher.Dispatcher, bindings string, debug bool) error {
func (b *Bridge) Run(dispatcher *messagedispatcher.Dispatcher, menumanager *menumanager.Manager, bindings string, debug bool) error {
// Ensure we cancel the context when we shutdown
defer b.cancel()
b.bindings = bindings
b.dispatcher = dispatcher
b.menumanager = menumanager
// Setup dialog handler
dialogClient := NewDialogClient(b.myLogger)
b.dialog = dispatcher.RegisterClient(dialogClient)
dialogClient.dispatcher = b.dialog
b.myLogger.Info("Bridge mode started.")
@@ -80,12 +94,12 @@ func (b *Bridge) wsBridgeHandler(w http.ResponseWriter, r *http.Request) {
func (b *Bridge) startSession(conn *websocket.Conn) {
// Create a new session for this connection
s := newSession(conn, b.bindings, b.dispatcher, b.myLogger, b.ctx)
s := newSession(conn, b.menumanager, b.bindings, b.dispatcher, b.myLogger, b.ctx)
// Setup the close handler
conn.SetCloseHandler(func(int, string) error {
b.myLogger.Info("Connection dropped [%s].", s.Identifier())
b.dispatcher.RemoveClient(s.client)
b.mu.Lock()
delete(b.sessions, s.Identifier())
b.mu.Unlock()

View File

@@ -6,6 +6,15 @@ import (
type BridgeClient struct {
session *session
// Tray menu cache to send to reconnecting clients
messageCache chan string
}
func NewBridgeClient() *BridgeClient {
return &BridgeClient{
messageCache: make(chan string, 100),
}
}
func (b BridgeClient) Quit() {
@@ -23,15 +32,15 @@ func (b BridgeClient) CallResult(message string) {
}
func (b BridgeClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
b.session.log.Info("OpenDialog unsupported in Bridge mode")
// Handled by dialog_client
}
func (b BridgeClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
b.session.log.Info("SaveDialog unsupported in Bridge mode")
// Handled by dialog_client
}
func (b BridgeClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
b.session.log.Info("MessageDialog unsupported in Bridge mode")
// Handled by dialog_client
}
func (b BridgeClient) WindowSetTitle(title string) {
@@ -103,11 +112,11 @@ func (b BridgeClient) SetApplicationMenu(menuJSON string) {
}
func (b BridgeClient) SetTrayMenu(trayMenuJSON string) {
b.session.log.Info("SetTrayMenu unsupported in Bridge mode")
b.session.sendMessage("TS" + trayMenuJSON)
}
func (b BridgeClient) UpdateTrayMenuLabel(JSON string) {
b.session.log.Info("UpdateTrayMenuLabel unsupported in Bridge mode")
func (b BridgeClient) UpdateTrayMenuLabel(trayMenuJSON string) {
b.session.sendMessage("TU" + trayMenuJSON)
}
func (b BridgeClient) UpdateContextMenu(contextMenuJSON string) {

View File

@@ -0,0 +1,141 @@
package bridge
import (
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
type DialogClient struct {
dispatcher *messagedispatcher.DispatchClient
log *logger.Logger
}
func NewDialogClient(log *logger.Logger) *DialogClient {
return &DialogClient{
log: log,
}
}
func (d *DialogClient) Quit() {
}
func (d *DialogClient) NotifyEvent(message string) {
}
func (d *DialogClient) CallResult(message string) {
}
func (d *DialogClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
}
func (d *DialogClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
}
func (d *DialogClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
osa, err := exec.LookPath("osascript")
if err != nil {
d.log.Info("MessageDialog unavailable (osascript not found)")
return
}
var btns slicer.StringSlicer
defaultButton := ""
cancelButton := ""
for index, btn := range dialogOptions.Buttons {
btns.Add(strconv.Quote(btn))
if btn == dialogOptions.DefaultButton {
defaultButton = fmt.Sprintf("default button %d", index+1)
}
if btn == dialogOptions.CancelButton {
cancelButton = fmt.Sprintf("cancel button %d", index+1)
}
}
buttons := "{" + btns.Join(",") + "}"
script := fmt.Sprintf("display dialog \"%s\" buttons %s %s %s with title \"%s\"", dialogOptions.Message, buttons, defaultButton, cancelButton, dialogOptions.Title)
go func() {
out, err := exec.Command(osa, "-e", script).Output()
if err != nil {
// Assume user has pressed cancel button
if dialogOptions.CancelButton != "" {
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + dialogOptions.CancelButton)
return
}
d.log.Error("Dialog had bad exit code. If this was a Cancel button, add 'CancelButton' to the dialog.MessageDialog struct. Error: %s", err.Error())
d.dispatcher.DispatchMessage("DM" + callbackID + "|error - check logs")
return
}
buttonPressed := strings.TrimSpace(strings.TrimPrefix(string(out), "button returned:"))
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + buttonPressed)
}()
}
func (d *DialogClient) WindowSetTitle(title string) {
}
func (d *DialogClient) WindowShow() {
}
func (d *DialogClient) WindowHide() {
}
func (d *DialogClient) WindowCenter() {
}
func (d *DialogClient) WindowMaximise() {
}
func (d *DialogClient) WindowUnmaximise() {
}
func (d *DialogClient) WindowMinimise() {
}
func (d *DialogClient) WindowUnminimise() {
}
func (d *DialogClient) WindowPosition(x int, y int) {
}
func (d *DialogClient) WindowSize(width int, height int) {
}
func (d *DialogClient) WindowSetMinSize(width int, height int) {
}
func (d *DialogClient) WindowSetMaxSize(width int, height int) {
}
func (d *DialogClient) WindowFullscreen() {
}
func (d *DialogClient) WindowUnFullscreen() {
}
func (d *DialogClient) WindowSetColour(colour int) {
}
func (d *DialogClient) DarkModeEnabled(callbackID string) {
}
func (d *DialogClient) SetApplicationMenu(menuJSON string) {
}
func (d *DialogClient) SetTrayMenu(trayMenuJSON string) {
}
func (d *DialogClient) UpdateTrayMenuLabel(trayMenuJSON string) {
}
func (d *DialogClient) UpdateContextMenu(contextMenuJSON string) {
}

View File

@@ -7,6 +7,8 @@ import (
"runtime"
"time"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/gorilla/websocket"
@@ -35,16 +37,20 @@ type session struct {
// client
client *messagedispatcher.DispatchClient
// Menus
menumanager *menumanager.Manager
}
func newSession(conn *websocket.Conn, bindings string, dispatcher *messagedispatcher.Dispatcher, logger *logger.Logger, ctx context.Context) *session {
func newSession(conn *websocket.Conn, menumanager *menumanager.Manager, bindings string, dispatcher *messagedispatcher.Dispatcher, logger *logger.Logger, ctx context.Context) *session {
result := &session{
conn: conn,
bindings: bindings,
log: logger,
shutdown: make(chan bool),
writeChan: make(chan []byte, 100),
ctx: ctx,
conn: conn,
bindings: bindings,
log: logger,
shutdown: make(chan bool),
writeChan: make(chan []byte, 100),
ctx: ctx,
menumanager: menumanager,
}
result.client = dispatcher.RegisterClient(newBridgeClient(result))
@@ -88,6 +94,16 @@ func (s *session) start(firstSession bool) {
s.sendMessage("b" + bootstrapMessage)
// Send menus
traymenus, err := s.menumanager.GetTrayMenus()
if err != nil {
s.log.Error(err.Error())
}
for _, trayMenu := range traymenus {
s.sendMessage("TS" + trayMenu)
}
for {
messageType, buffer, err := s.conn.ReadMessage()
if messageType == -1 {
@@ -95,7 +111,8 @@ func (s *session) start(firstSession bool) {
}
if err != nil {
s.log.Error("Error reading message: %v", err)
continue
err = s.conn.Close()
return
}
message := string(buffer)

View File

@@ -785,7 +785,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
buttonPressed = button4;
}
// Construct callback message. Format "DS<callbackID>|<selected button index>"
// Construct callback message. Format "DM<callbackID>|<selected button index>"
const char *callback = concat("DM", callbackID);
const char *header = concat(callback, "|");
const char *responseMessage = concat(header, buttonPressed);

View File

@@ -575,7 +575,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
return item;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA) {
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
@@ -598,6 +598,9 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
if( templateImage ) {
msg(nsimage, s("template"), YES);
}
msg(item, s("setImage:"), nsimage);
}
@@ -740,6 +743,9 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
const char *image = getJSONString(item, "Image");
const char *fontName = getJSONString(item, "FontName");
const char *RGBA = getJSONString(item, "RGBA");
bool templateImage = false;
getJSONBool(item, "MacTemplateImage", &templateImage);
int fontSize = 12;
getJSONInt(item, "FontSize", &fontSize);
@@ -775,7 +781,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
if( type != NULL ) {
if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA);
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage);
}
else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu);

View File

@@ -105,7 +105,7 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData);

View File

@@ -2,6 +2,7 @@ package menumanager
import (
"encoding/json"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
)
@@ -34,7 +35,8 @@ type ProcessedMenuItem struct {
FontName string `json:",omitempty"`
// Image - base64 image data
Image string `json:",omitempty"`
Image string `json:",omitempty"`
MacTemplateImage bool `json:", omitempty"`
// Tooltip
Tooltip string `json:",omitempty"`
@@ -44,20 +46,21 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
ID := menuItemMap.menuItemToIDMap[menuItem]
result := &ProcessedMenuItem{
ID: ID,
Label: menuItem.Label,
Role: menuItem.Role,
Accelerator: menuItem.Accelerator,
Type: menuItem.Type,
Disabled: menuItem.Disabled,
Hidden: menuItem.Hidden,
Checked: menuItem.Checked,
SubMenu: nil,
RGBA: menuItem.RGBA,
FontSize: menuItem.FontSize,
FontName: menuItem.FontName,
Image: menuItem.Image,
Tooltip: menuItem.Tooltip,
ID: ID,
Label: menuItem.Label,
Role: menuItem.Role,
Accelerator: menuItem.Accelerator,
Type: menuItem.Type,
Disabled: menuItem.Disabled,
Hidden: menuItem.Hidden,
Checked: menuItem.Checked,
SubMenu: nil,
RGBA: menuItem.RGBA,
FontSize: menuItem.FontSize,
FontName: menuItem.FontName,
Image: menuItem.Image,
MacTemplateImage: menuItem.MacTemplateImage,
Tooltip: menuItem.Tooltip,
}
if menuItem.SubMenu != nil {

View File

@@ -27,7 +27,7 @@ func dialogMessageParser(message string) (*parsedMessage, error) {
if idx < 0 {
return nil, fmt.Errorf("Invalid dialog response message format: %+v", message)
}
callbackID := message[:idx+1]
callbackID := message[:idx]
payloadData := message[idx+1:]
switch dialogType {

View File

@@ -1,6 +1,7 @@
package process
import (
"context"
"os"
"os/exec"
@@ -9,18 +10,16 @@ import (
// Process defines a process that can be executed
type Process struct {
logger *clilogger.CLILogger
cmd *exec.Cmd
exitChannel chan bool
Running bool
logger *clilogger.CLILogger
cmd *exec.Cmd
running bool
}
// NewProcess creates a new process struct
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
func NewProcess(ctx context.Context, logger *clilogger.CLILogger, cmd string, args ...string) *Process {
result := &Process{
logger: logger,
cmd: exec.Command(cmd, args...),
exitChannel: make(chan bool, 1),
logger: logger,
cmd: exec.CommandContext(ctx, cmd, args...),
}
result.cmd.Stdout = os.Stdout
result.cmd.Stderr = os.Stderr
@@ -35,30 +34,30 @@ func (p *Process) Start() error {
return err
}
p.Running = true
p.running = true
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
cmd.Wait()
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
*running = false
exitChannel <- true
}(p.cmd, &p.Running, p.logger, p.exitChannel)
go func(p *Process) {
p.logger.Println("Starting process (PID: %d)", p.cmd.Process.Pid)
err := p.cmd.Wait()
if err != nil {
p.running = false
p.logger.Println("Process failed to run: %s", err.Error())
return
}
p.logger.Println("Exiting process (PID: %d)", p.cmd.Process.Pid)
}(p)
return nil
}
// Kill the process
func (p *Process) Kill() error {
if !p.Running {
return nil
if p.running {
println("Calling kill")
p.running = false
return p.cmd.Process.Kill()
}
err := p.cmd.Process.Kill()
// Wait for command to exit properly
<-p.exitChannel
return err
return nil
}
// PID returns the process PID

View File

@@ -0,0 +1 @@
src

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ The lightweight framework for web-like apps
*/
/* jshint esversion: 6 */
import { InitBridge } from './bridge';
import bridge from './bridge';
/**
* ready will execute the callback when Wails has loaded
@@ -25,7 +25,7 @@ function ready(callback) {
}
// If not we need to setup the bridge
InitBridge(callback);
bridge.InitBridge(callback);
}
module.exports = {

View File

@@ -1,6 +1,6 @@
{
"name": "@wails/runtime",
"version": "1.3.7",
"version": "1.3.9",
"description": "Wails V2 Javascript runtime library",
"main": "main.js",
"types": "runtime.d.ts",

View File

@@ -0,0 +1,74 @@
<script>
export let menu;
console.log({menu})
export let visible = false;
function click(id) {
console.log("MenuItem", id, "pressed")
}
</script>
{#if visible}
<div class="menu">
{#if menu.Menu }
{#each menu.Menu.Items as menuItem}
{#if menuItem.Type === "Text" }
<div class="menuitem" on:click={() => click(menuItem.ID)}>
{#if menuItem.Image }
<div><img alt="" src="data:image/png;base64,{menuItem.Image}"/></div>
{/if}
<div class="menulabel">{menuItem.Label}</div>
</div>
{:else if menuItem.Type === "Separator"}
<div class="separator"><hr/></div>
{/if}
{/each}
{/if}
</div>
{/if}
<style>
.menu {
padding: 5px;
background-color: #0008;
color: #EEF;
border-radius: 5px;
margin-top: 5px;
position: absolute;
backdrop-filter: blur(3px) saturate(160%) contrast(45%) brightness(140%);
border: 1px solid rgb(88,88,88);
box-shadow: 0 0 1px rgb(146,146,148) inset;
}
.menuitem {
display: flex;
align-items: center;
padding: 1px 5px;
}
.menuitem:hover {
display: flex;
align-items: center;
background-color: rgb(57,131,223);
padding: 1px 5px;
border-radius: 5px;
}
.menuitem img {
padding-right: 5px;
}
.separator {
padding-top: 5px;
width: 100%;
padding-bottom: 5px;
}
.separator:hover {
background-color: #0000;
}
</style>

View File

@@ -0,0 +1,61 @@
<script>
import {menuVisible} from './store'
import {fade} from 'svelte/transition';
import {trays} from './store'
import TrayMenu from "./TrayMenu.svelte";
import {onMount} from "svelte";
let time = new Date();
$: day = time.toLocaleString("default", { weekday: "short" })
$: dom = time.getDate()
$: mon = time.toLocaleString("default", { month: "short" })
$: currentTime = time.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }).toLowerCase()
$: dateTimeString = `${day} ${dom} ${mon} ${currentTime}`
onMount(() => {
const interval = setInterval(() => {
time = new Date();
}, 1000);
return () => {
clearInterval(interval);
};
});
</script>
{#if $menuVisible }
<div class="wails-menubar" transition:fade>
<span class="tray-menus">
{#each $trays as tray}
<TrayMenu {tray}/>
{/each}
<span class="time">{dateTimeString}</span>
</span>
</div>
{/if}
<style>
.tray-menus {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.wails-menubar { position: relative;
display: block;
top: 0;
height: 2rem;
width: 100%;
border-bottom: 1px solid #b3b3b3;
box-shadow: 0 0 10px 0 #33333360;
}
.time {
padding-left: 0.5rem;
padding-right: 1.5rem;
overflow: visible;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,55 @@
<script>
import { overlayVisible } from './store'
import { fade, } from 'svelte/transition';
</script>
{#if $overlayVisible }
<div class="wails-reconnect-overlay" transition:fade="{{ duration: 200 }}">
<div class="wails-reconnect-overlay-content">
<div class="wails-reconnect-overlay-loadingspinner"></div>
</div>
</div>
{/if}
<style>
.wails-reconnect-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
backdrop-filter: blur(20px) saturate(160%) contrast(45%) brightness(140%);
z-index: 999999
}
.wails-reconnect-overlay-content {
position: relative;
top: 50%;
transform: translateY(-50%);
margin: 0;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAflBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAAAAABAQEEBAQAAAAAAAAEBAQAAAADAwMAAAABAQEAAAAAAAAAAAAAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWCC3waAAAAKXRSTlMALgUMIBk0+xEqJs70Xhb3lu3EjX2EZTlv5eHXvbarQj3cdmpXSqOeUDwaqNAAAAKCSURBVEjHjZTntqsgEIUPVVCwtxg1vfD+L3hHRe8K6snZf+KKn8OewvzsSSeXLruLnz+KHs0gr6DkT3xsRkU6VVn4Ha/UxLe1Z4y64i847sykPBh/AvQ7ry3eFN70oKrfcBJYvm/tQ1qxP4T3emXPeXAkvodPUvtdjbhk+Ft4c0hslTiXVOzxOJ15NWUblQhRsdu3E1AfCjj3Gdm18zSOsiH8Lk4TB480ksy62fiqNo4OpyU8O21l6+hyRtS6z8r1pHlmle5sR1/WXS6Mq2Nl+YeKt3vr+vdH/q4O68tzXuwkiZmngYb4R8Co1jh0+Ww2UTyWxBvtyxLO7QVjO3YOD/lWZpbXDGellFG2Mws58mMnjVZSn7p+XvZ6IF4nn02OJZV0aTO22arp/DgLPtrgpVoi6TPbZm4XQBjY159w02uO0BDdYsfrOEi0M2ulRXlCIPAOuN1NOVhi+riBR3dgwQplYsZRZJLXq23Mlo5njkbY0rZFu3oiNIYG2kqsbVz67OlNuZZIOlfxHDl0UpyRX86z/OYC/3qf1A1xTrMp/PWWM4ePzf8DDp1nesQRpcFk7BlwdzN08ZIALJpCaciQXO0f6k4dnuT/Ewg4l7qSTNzm2SykdHn6GJ12mWc6aCNj/g1cTXpB8YFfr0uVc96aFkkqiIiX4nO+salKwGtIkvfB+Ja8DxMeD3hIXP5mTOYPB4eVT0+32I5ykvPZjesnkGgIREgYnmLrPb0PdV3hoLup2TjcGBPM4mgsfF5BrawZR4/GpzYQzQfrUZCf0TCWYo2DqhdhTJBQ6j4xqmmLN5LjdRIY8LWExiFUsSrza/nmFBqw3I9tEZB9h0lIQSO9if8DkISDAj8CDawAAAAASUVORK5CYII=);
background-repeat: no-repeat;
background-position: center
}
.wails-reconnect-overlay-loadingspinner {
pointer-events: none;
width: 2.5em;
height: 2.5em;
border: .4em solid transparent;
border-color: #f00 #eee0 #f00 #eee0;
border-radius: 50%;
animation: loadingspin 1s linear infinite;
margin: auto;
padding: 2.5em
}
@keyframes loadingspin {
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,66 @@
<script>
import Menu from "./Menu.svelte";
import { selectedMenu } from "./store";
export let tray = null;
$: visible = $selectedMenu === tray;
function closeMenu() {
selectedMenu.set(null);
}
function trayClicked() {
if ( $selectedMenu !== tray ) {
selectedMenu.set(tray);
} else {
selectedMenu.set(null);
}
}
// Source: https://svelte.dev/repl/0ace7a508bd843b798ae599940a91783?version=3.16.7
/** Dispatch event on click outside of node */
function clickOutside(node) {
const handleClick = event => {
if (node && !node.contains(event.target) && !event.defaultPrevented) {
console.log("click outside of node")
node.dispatchEvent(
new CustomEvent('click_outside', node)
)
}
}
document.addEventListener('click', handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
}
}
</script>
<span class="tray-menu" use:clickOutside on:click_outside={closeMenu}>
<!--{#if tray.Image && tray.Image.length > 0}-->
<!-- <img alt="" src="data:image/png;base64,{tray.Image}"/>-->
<!--{/if}-->
<span class="label" on:click={trayClicked}>{tray.Label}</span>
{#if tray.ProcessedMenu }
<Menu menu="{tray.ProcessedMenu}" visible="{visible}"/>
{/if}
</span>
<style>
.tray-menu {
padding-left: 0.5rem;
padding-right: 0.5rem;
overflow: visible;
font-size: 14px;
}
.label {
text-align: right;
padding-right: 10px;
}
</style>

View File

@@ -0,0 +1,9 @@
export function log(message) {
// eslint-disable-next-line
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'
);
}

View File

@@ -0,0 +1,45 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import Overlay from './Overlay.svelte';
import MenuBar from './Menubar.svelte';
import {showOverlay} from "./store";
import {StartWebsocket} from "./websocket";
let components = {};
function setupMenuBar() {
components.menubar = new MenuBar({
target: document.body,
});
}
// Sets up the overlay
function setupOverlay() {
components.overlay = new Overlay({
target: document.body,
anchor: document.querySelector('#wails-bridge'),
});
}
export function InitBridge(callback) {
setupMenuBar()
// Setup the overlay
setupOverlay();
// Start by showing the overlay...
showOverlay();
// ...and attempt to connect
StartWebsocket(callback);
}

View File

@@ -0,0 +1,525 @@
{
"name": "bridge",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
"dev": true,
"requires": {
"@babel/highlight": "^7.12.13"
}
},
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
"dev": true
},
"@babel/highlight": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz",
"integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@rollup/plugin-commonjs": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz",
"integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
"commondir": "^1.0.1",
"estree-walker": "^2.0.1",
"glob": "^7.1.6",
"is-reference": "^1.2.1",
"magic-string": "^0.25.7",
"resolve": "^1.17.0"
},
"dependencies": {
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
}
}
},
"@rollup/plugin-node-resolve": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.1.1.tgz",
"integrity": "sha512-zlBXR4eRS+2m79TsUZWhsd0slrHUYdRx4JF+aVQm+MI0wsKdlpC2vlDVjmlGvtZY1vsefOT9w3JxvmWSBei+Lg==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
"@types/resolve": "1.17.1",
"builtin-modules": "^3.1.0",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.19.0"
}
},
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"@types/node": {
"version": "14.14.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz",
"integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==",
"dev": true
},
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"builtin-modules": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
"integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"is-core-module": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
"dev": true
},
"is-reference": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
"dev": true,
"requires": {
"@types/estree": "*"
}
},
"jest-worker": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
"dev": true,
"requires": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^7.0.0"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.4"
}
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
"dev": true
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"requires": {
"safe-buffer": "^5.1.0"
}
},
"require-relative": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
"dev": true
},
"resolve": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
"integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
"dev": true,
"requires": {
"is-core-module": "^2.1.0",
"path-parse": "^1.0.6"
}
},
"rollup": {
"version": "2.38.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.5.tgz",
"integrity": "sha512-VoWt8DysFGDVRGWuHTqZzT02J0ASgjVq/hPs9QcBOGMd7B+jfTr/iqMVEyOi901rE3xq+Deq66GzIT1yt7sGwQ==",
"dev": true,
"requires": {
"fsevents": "~2.3.1"
}
},
"rollup-plugin-svelte": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.0.tgz",
"integrity": "sha512-vopCUq3G+25sKjwF5VilIbiY6KCuMNHP1PFvx2Vr3REBNMDllKHFZN2B9jwwC+MqNc3UPKkjXnceLPEjTjXGXg==",
"dev": true,
"requires": {
"require-relative": "^0.8.7",
"rollup-pluginutils": "^2.8.2"
}
},
"rollup-plugin-terser": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"jest-worker": "^26.2.1",
"serialize-javascript": "^4.0.0",
"terser": "^5.0.0"
}
},
"rollup-pluginutils": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
"dev": true,
"requires": {
"estree-walker": "^0.6.1"
},
"dependencies": {
"estree-walker": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
"dev": true
}
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
},
"serialize-javascript": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
}
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"svelte": {
"version": "3.32.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.32.2.tgz",
"integrity": "sha512-Zxh1MQQl/+vnToKbU1Per+PoMN8Jb2MeKJcGxiOsCGR677hXw7jkMfbnNXq33+dxIzV/HfA4xtoSPJrqeB0VUg==",
"dev": true
},
"terser": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz",
"integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.19"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

View File

@@ -0,0 +1,21 @@
{
"name": "bridge",
"version": "1.0.0",
"description": "Wails bridge",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npx rollup -c",
"watch": "npx rollup -c -w"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"rollup": "^2.38.5",
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.32.2"
}
}

View File

@@ -0,0 +1,37 @@
import resolve from '@rollup/plugin-node-resolve';
// import commonjs from '@rollup/plugin-commonjs';
import svelte from 'rollup-plugin-svelte';
import { terser } from "rollup-plugin-terser";
export default [
// browser-friendly UMD build
{
input: 'main.js',
output: {
name: 'bridge',
file: '../bridge.js',
format: 'umd',
exports: "named"
},
plugins: [
svelte({
// Optionally, preprocess components with svelte.preprocess:
// https://svelte.dev/docs#svelte_preprocess
// preprocess: {
// style: ({content}) => {
// return transformStyles(content);
// }
// },
// Emit CSS as "files" for other plugins to process. default is true
emitCss: false,
}),
resolve({browser: true}), // so Rollup can find `ms`
// commonjs() // so Rollup can convert `ms` to an ES module
// terser(),
]
},
];

View File

@@ -0,0 +1,52 @@
import { writable } from 'svelte/store';
import {log} from "./log";
/** Overlay */
export const overlayVisible = writable(false);
export function showOverlay() {
overlayVisible.set(true);
}
export function hideOverlay() {
overlayVisible.set(false);
}
/** Menubar **/
export const menuVisible = writable(true);
export function showMenuBar() {
menuVisible.set(true);
}
export function hideMenuBar() {
menuVisible.set(false);
}
/** Trays **/
export const trays = writable([]);
export function setTray(tray) {
trays.update((current) => {
// Remove existing if it exists, else add
const index = current.findIndex(item => item.ID === tray.ID);
if ( index === -1 ) {
current.push(tray);
} else {
current[index] = tray;
}
return current;
})
}
export function updateTrayLabel(tray) {
trays.update((current) => {
// Remove existing if it exists, else add
const index = current.findIndex(item => item.ID === tray.ID);
if ( index === -1 ) {
return log("ERROR: Attempted to update tray index ", tray.ID, "but it doesn't exist")
}
current[index].Label = tray.Label;
return current;
})
}
export let selectedMenu = writable(null);

View File

@@ -0,0 +1,165 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import {setTray, hideOverlay, showOverlay, updateTrayLabel} from "./store";
import {log} from "./log";
let websocket = null;
let callback = null;
let connectTimer;
export function StartWebsocket(userCallback) {
callback = userCallback;
window.onbeforeunload = function() {
if( websocket ) {
websocket.onclose = function () { };
websocket.close();
websocket = null;
}
}
// ...and attempt to connect
connect();
}
function setupIPCBridge() {
// darwin
window.webkit = {
messageHandlers: {
external: {
postMessage: (message) => {
websocket.send(message);
}
},
windowDrag: {
postMessage: () => {
// Ignore window drag events
}
}
}
};
}
// Handles incoming websocket connections
function handleConnect() {
log('Connected to backend');
setupIPCBridge();
hideOverlay();
clearInterval(connectTimer);
websocket.onclose = handleDisconnect;
websocket.onmessage = handleMessage;
}
// Handles websocket disconnects
function handleDisconnect() {
log('Disconnected from backend');
websocket = null;
showOverlay();
connect();
}
// Try to connect to the backend every 1s (default value).
function connect() {
connectTimer = setInterval(function () {
if (websocket == null) {
websocket = new WebSocket('ws://' + window.location.hostname + ':34115/bridge');
websocket.onopen = handleConnect;
websocket.onerror = function (e) {
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
websocket = null;
return false;
};
}
}, 1000);
}
// Adds a script to the Dom.
// Removes it if second parameter is true.
function addScript(script, remove) {
const s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.textContent = script;
document.head.appendChild(s);
// Remove internal messages from the DOM
if (remove) {
s.parentNode.removeChild(s);
}
}
function handleMessage(message) {
// As a bridge we ignore js and css injections
switch (message.data[0]) {
// Wails library - inject!
case 'b':
message = message.data.slice(1)
addScript(message);
log('Loaded Wails Runtime');
// We need to now send a message to the backend telling it
// we have loaded (System Start)
window.webkit.messageHandlers.external.postMessage("SS");
// Now wails runtime is loaded, wails for the ready event
// and callback to the main app
// window.wails.Events.On('wails:loaded', function () {
if (callback) {
log('Notifying application');
callback(window.wails);
}
// });
break;
// // Notifications
// case 'n':
// addScript(message.data.slice(1), true);
// break;
// // Binding
// case 'b':
// const binding = message.data.slice(1);
// //log("Binding: " + binding)
// window.wails._.NewBinding(binding);
// break;
// // Call back
case 'c':
const callbackData = message.data.slice(1);
window.wails._.Callback(callbackData);
break;
// Tray
case 'T':
const trayMessage = message.data.slice(1);
switch (trayMessage[0]) {
case 'S':
// Set tray
const trayJSON = trayMessage.slice(1);
let tray = JSON.parse(trayJSON)
setTray(tray)
break
case 'U':
// Update label
const updateTrayLabelJSON = trayMessage.slice(1);
let trayLabelData = JSON.parse(updateTrayLabelJSON)
updateTrayLabel(trayLabelData)
break
default:
log('Unknown tray message: ' + message.data);
}
break;
default:
log('Unknown message: ' + message.data);
}
}

View File

@@ -80,4 +80,5 @@ func (m *Manager) Start() {
m.logger.Trace("Shutdown")
m.wg.Done()
}()
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"sync"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime"
@@ -30,6 +31,9 @@ type Runtime struct {
//ctx
ctx context.Context
// Startup Hook
startupOnce sync.Once
}
// NewRuntime creates a new runtime subsystem
@@ -75,7 +79,9 @@ func (r *Runtime) Start() error {
switch hook {
case "startup":
if r.startupCallback != nil {
go r.startupCallback(r.runtime)
r.startupOnce.Do(func() {
go r.startupCallback(r.runtime)
})
} else {
r.logger.Warning("no startup callback registered!")
}

View File

@@ -1,8 +1,9 @@
package menu
import (
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"sync"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
)
// MenuItem represents a menuitem contained in a menu
@@ -38,6 +39,9 @@ type MenuItem struct {
// Image - base64 image data
Image string
// MacTemplateImage indicates that on a mac, this image is a template image
MacTemplateImage bool
// Tooltip
Tooltip string