mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 07:21:32 +08:00
merge exp branch
This commit is contained in:
parent
18d0ed890a
commit
81645190f0
3
.gitignore
vendored
3
.gitignore
vendored
@ -31,3 +31,6 @@ v2/test/kitchensink/frontend/package.json.md5
|
|||||||
v2/cmd/wails/internal/commands/initialise/templates/testtemplates/
|
v2/cmd/wails/internal/commands/initialise/templates/testtemplates/
|
||||||
.env
|
.env
|
||||||
/website/static/img/.cache.json
|
/website/static/img/.cache.json
|
||||||
|
|
||||||
|
/v3/.task
|
||||||
|
/v3/examples/build/bin/testapp
|
||||||
|
@ -4,12 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/application"
|
"github.com/wailsapp/wails/v2/pkg/application"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed all:frontend/dist
|
//go:embed all:frontend/dist
|
||||||
|
8
v3/.gitignore
vendored
Normal file
8
v3/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
examples/kitchensink/kitchensink
|
||||||
|
cmd/wails/wails
|
||||||
|
/examples/systray/systray
|
||||||
|
/examples/window/window
|
||||||
|
/examples/dialogs/dialogs
|
||||||
|
/examples/menu/menu
|
||||||
|
/examples/clipboard/clipboard
|
||||||
|
/examples/plain/plain
|
136
v3/Taskfile.yaml
Normal file
136
v3/Taskfile.yaml
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# https://taskfile.dev
|
||||||
|
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
build-runtime-debug:
|
||||||
|
dir: internal/runtime
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- npx esbuild desktop/main.js --bundle --sourcemap=inline --outfile=runtime_debug_desktop_{{.PLATFORM}}.js --define:DEBUG=true --define:WINDOWS={{.WINDOWS}} --define:DARWIN={{.DARWIN}} --define:LINUX={{.LINUX}} --define:PLATFORM={{.PLATFORM}}
|
||||||
|
|
||||||
|
build-runtime-debug-windows:
|
||||||
|
cmds:
|
||||||
|
- task: build-runtime-debug
|
||||||
|
vars:
|
||||||
|
WINDOWS: true
|
||||||
|
DARWIN: false
|
||||||
|
LINUX: false
|
||||||
|
PLATFORM: windows
|
||||||
|
|
||||||
|
build-runtime-debug-linux:
|
||||||
|
cmds:
|
||||||
|
- task: build-runtime-debug
|
||||||
|
vars:
|
||||||
|
WINDOWS: false
|
||||||
|
DARWIN: false
|
||||||
|
LINUX: true
|
||||||
|
PLATFORM: linux
|
||||||
|
|
||||||
|
build-runtime-debug-darwin:
|
||||||
|
cmds:
|
||||||
|
- task: build-runtime-debug
|
||||||
|
vars:
|
||||||
|
WINDOWS: false
|
||||||
|
DARWIN: true
|
||||||
|
LINUX: false
|
||||||
|
PLATFORM: darwin
|
||||||
|
|
||||||
|
build-runtime-production:
|
||||||
|
dir: internal/runtime
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- npx esbuild desktop/main.js --bundle --minify --outfile=runtime_production_desktop_{{.PLATFORM}}.js --define:DEBUG=true --define:WINDOWS={{.WINDOWS}} --define:DARWIN={{.DARWIN}} --define:LINUX={{.LINUX}} --define:PLATFORM={{.PLATFORM}}
|
||||||
|
|
||||||
|
build-runtime-production-windows:
|
||||||
|
cmds:
|
||||||
|
- task: build-runtime-production
|
||||||
|
vars:
|
||||||
|
WINDOWS: true
|
||||||
|
DARWIN: false
|
||||||
|
LINUX: false
|
||||||
|
PLATFORM: windows
|
||||||
|
|
||||||
|
build-runtime-production-linux:
|
||||||
|
cmds:
|
||||||
|
- task: build-runtime-production
|
||||||
|
vars:
|
||||||
|
WINDOWS: false
|
||||||
|
DARWIN: false
|
||||||
|
LINUX: true
|
||||||
|
PLATFORM: linux
|
||||||
|
|
||||||
|
build-runtime-production-darwin:
|
||||||
|
cmds:
|
||||||
|
- task: build-runtime-production
|
||||||
|
vars:
|
||||||
|
WINDOWS: false
|
||||||
|
DARWIN: true
|
||||||
|
LINUX: false
|
||||||
|
PLATFORM: darwin
|
||||||
|
|
||||||
|
install-runtime-dev-deps:
|
||||||
|
dir: internal/runtime/dev
|
||||||
|
internal: true
|
||||||
|
sources:
|
||||||
|
- package.json
|
||||||
|
cmds:
|
||||||
|
- npm install
|
||||||
|
|
||||||
|
|
||||||
|
install-runtime-deps:
|
||||||
|
dir: internal/runtime
|
||||||
|
internal: true
|
||||||
|
sources:
|
||||||
|
- package.json
|
||||||
|
cmds:
|
||||||
|
- npm install
|
||||||
|
|
||||||
|
build-runtime-dev:
|
||||||
|
dir: internal/runtime/dev
|
||||||
|
deps:
|
||||||
|
- install-runtime-dev-deps
|
||||||
|
sources:
|
||||||
|
- ./*.js
|
||||||
|
generates:
|
||||||
|
- ../ipc_websocket.js
|
||||||
|
cmds:
|
||||||
|
- node build.js
|
||||||
|
|
||||||
|
build-runtime-ipc:
|
||||||
|
dir: internal/runtime
|
||||||
|
deps:
|
||||||
|
- install-runtime-dev-deps
|
||||||
|
sources:
|
||||||
|
- ./desktop/ipc.js
|
||||||
|
generates:
|
||||||
|
- ipc.js
|
||||||
|
cmds:
|
||||||
|
- npx esbuild desktop/ipc.js --bundle --minify --outfile=ipc.js
|
||||||
|
|
||||||
|
test-runtime:
|
||||||
|
dir: internal/runtime
|
||||||
|
cmds:
|
||||||
|
- npx vitest
|
||||||
|
|
||||||
|
build-runtime-all:
|
||||||
|
dir: internal/runtime
|
||||||
|
deps:
|
||||||
|
- build-runtime-production-darwin
|
||||||
|
- build-runtime-production-windows
|
||||||
|
- build-runtime-production-linux
|
||||||
|
- build-runtime-debug-darwin
|
||||||
|
- build-runtime-debug-windows
|
||||||
|
- build-runtime-debug-linux
|
||||||
|
- build-runtime-dev
|
||||||
|
- build-runtime-ipc
|
||||||
|
cmds:
|
||||||
|
- task: test-runtime
|
||||||
|
|
||||||
|
build-runtime:
|
||||||
|
dir: internal/runtime
|
||||||
|
deps:
|
||||||
|
- install-runtime-deps
|
||||||
|
cmds:
|
||||||
|
- task: build-runtime-all
|
79
v3/cmd/wails/README.md
Normal file
79
v3/cmd/wails/README.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# The Wails CLI
|
||||||
|
|
||||||
|
The Wails CLI is a command line tool that allows you to create, build and run Wails applications.
|
||||||
|
There are a number of commands related to tooling, such as icon generation and asset bundling.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### run
|
||||||
|
|
||||||
|
The run command is for running tasks defined in `Taskfile.yml`.
|
||||||
|
|
||||||
|
| Flag | Type | Description | Default |
|
||||||
|
|--------------------|--------|------------------------------------------------------|-----------------------|
|
||||||
|
| `-t` | string | The name of the task to run | |
|
||||||
|
|
||||||
|
### generate
|
||||||
|
|
||||||
|
The `generate` command is used to generate resources and assets for your Wails project.
|
||||||
|
It can be used to generate many things including:
|
||||||
|
- application icons,
|
||||||
|
- resource files for Windows applications
|
||||||
|
- Info.plist files for macOS deployments
|
||||||
|
|
||||||
|
#### icon
|
||||||
|
|
||||||
|
The `icon` command generates icons for your project.
|
||||||
|
|
||||||
|
| Flag | Type | Description | Default |
|
||||||
|
|--------------------|--------|------------------------------------------------------|-----------------------|
|
||||||
|
| `-example` | bool | Generates example icon file (appicon.png) | |
|
||||||
|
| `-input` | string | The input image file | |
|
||||||
|
| `-sizes` | string | The sizes to generate in .ico file (comma separated) | "256,128,64,48,32,16" |
|
||||||
|
| `-windowsFilename` | string | The output filename for the Windows icon | icons.ico |
|
||||||
|
| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wails generate icon -input myicon.png -sizes "32,64,128" -windowsFilename myicon.ico -macFilename myicon.icns
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate icons for mac and windows and save them in the current directory as `myicon.ico`
|
||||||
|
and `myicons.icns`.
|
||||||
|
|
||||||
|
#### syso
|
||||||
|
|
||||||
|
The `syso` command generates a Windows resource file (aka `.syso`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wails generate syso <options>
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Type | Description | Default |
|
||||||
|
|-------------|--------|--------------------------------------------|------------------|
|
||||||
|
| `-example` | bool | Generates example manifest & info files | |
|
||||||
|
| `-manifest` | string | The manifest file | |
|
||||||
|
| `-info` | string | The info.json file | |
|
||||||
|
| `-icon` | string | The icon file | |
|
||||||
|
| `-out` | string | The output filename for the syso file | `wails.exe.syso` |
|
||||||
|
| `-arch` | string | The target architecture (amd64,arm64,386) | `runtime.GOOS` |
|
||||||
|
|
||||||
|
If `-example` is provided, the command will generate example manifest and info files
|
||||||
|
in the current directory and exit.
|
||||||
|
|
||||||
|
If `-manifest` is provided, the command will use the provided manifest file to generate
|
||||||
|
the syso file.
|
||||||
|
|
||||||
|
If `-info` is provided, the command will use the provided info.json file to set the version
|
||||||
|
information in the syso file.
|
||||||
|
|
||||||
|
NOTE: We use [winres](https://github.com/tc-hib/winres) to generate the syso file. Please
|
||||||
|
refer to the winres documentation for more information.
|
||||||
|
|
||||||
|
NOTE: Whilst the tool will work for 32-bit Windows, it is not supported. Please use 64-bit.
|
||||||
|
|
||||||
|
#### defaults
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wails icon -input myicon.png -sizes "32,64,128" -windowsFilename myicon.ico -macFilename myicon.icns
|
||||||
|
```
|
||||||
|
This will generate all the default assets and resources in the current directory. I
|
28
v3/cmd/wails/main.go
Normal file
28
v3/cmd/wails/main.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
|
||||||
|
"github.com/leaanthony/clir"
|
||||||
|
"github.com/wailsapp/wails/v3/internal/commands"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := clir.NewCli("wails", "The Wails CLI", "v3")
|
||||||
|
app.NewSubCommandFunction("init", "Initialise a new project", commands.Init)
|
||||||
|
task := app.NewSubCommand("task", "Run and list tasks")
|
||||||
|
task.NewSubCommandFunction("run", "Run a task", commands.RunTask)
|
||||||
|
task.NewSubCommandFunction("list", "List tasks", commands.ListTasks)
|
||||||
|
generate := app.NewSubCommand("generate", "Generation tools")
|
||||||
|
generate.NewSubCommandFunction("defaults", "Generate default build assets", commands.Defaults)
|
||||||
|
generate.NewSubCommandFunction("icons", "Generate icons", commands.GenerateIcons)
|
||||||
|
generate.NewSubCommandFunction("syso", "Generate Windows .syso file", commands.GenerateSyso)
|
||||||
|
generate.NewSubCommandFunction("bindings", "Generate bindings + models", commands.GenerateBindings)
|
||||||
|
err := app.Run()
|
||||||
|
if err != nil {
|
||||||
|
pterm.Error.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
31
v3/examples/binding/main.go
Normal file
31
v3/examples/binding/main.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/examples/binding/services"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type localStruct struct{}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Bind: []interface{}{
|
||||||
|
&localStruct{},
|
||||||
|
&services.GreetService{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
v3/examples/binding/models/person.go
Normal file
5
v3/examples/binding/models/person.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
}
|
23
v3/examples/binding/services/GreetService.go
Normal file
23
v3/examples/binding/services/GreetService.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wailsapp/wails/v3/examples/binding/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GreetService struct {
|
||||||
|
SomeVariable int
|
||||||
|
lowercase string
|
||||||
|
Parent *models.Person
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GreetService) Greet(name string) string {
|
||||||
|
return "Hello " + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GreetService) GetPerson() *models.Person {
|
||||||
|
return g.Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GreetService) SetPerson(person *models.Person) {
|
||||||
|
g.Parent = person
|
||||||
|
}
|
15
v3/examples/build/README.md
Normal file
15
v3/examples/build/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Build
|
||||||
|
|
||||||
|
Wails has adopted [Taskfile](https://taskfile.dev) as its build tool. This is optional
|
||||||
|
and any build tool can be used. However, Taskfile is a great tool, and we recommend it.
|
||||||
|
|
||||||
|
The Wails CLI has built-in integration with Taskfile so the standalone version is not a
|
||||||
|
requirement.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the example, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wails run -t build
|
||||||
|
```
|
42
v3/examples/build/Taskfile.yml
Normal file
42
v3/examples/build/Taskfile.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
pre-build:
|
||||||
|
summary: Pre-build hooks
|
||||||
|
|
||||||
|
post-build:
|
||||||
|
summary: Post-build hooks
|
||||||
|
|
||||||
|
build:
|
||||||
|
summary: Builds the application
|
||||||
|
cmds:
|
||||||
|
- task: pre-build
|
||||||
|
- go build -gcflags=all="-N -l" -o bin/testapp main.go
|
||||||
|
- task: post-build
|
||||||
|
env:
|
||||||
|
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||||
|
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||||
|
|
||||||
|
generate-icons:
|
||||||
|
summary: Generates Windows `.ico` and Mac `.icns` files from an image
|
||||||
|
dir: build
|
||||||
|
cmds:
|
||||||
|
# Generates both .ico and .icns files
|
||||||
|
- wails generate icon -input appicon.png
|
||||||
|
|
||||||
|
build-prod:
|
||||||
|
summary: Creates a production build of the application
|
||||||
|
cmds:
|
||||||
|
- go build -tags production -ldflags="-w -s" -o bin/testapp
|
||||||
|
|
||||||
|
package-darwin:
|
||||||
|
summary: Packages a production build of the application into a `.app` bundle
|
||||||
|
deps:
|
||||||
|
- build-prod
|
||||||
|
- generate-icons
|
||||||
|
cmds:
|
||||||
|
- mkdir -p buildtest.app/Contents/{MacOS,Resources}
|
||||||
|
- cp build/icons.icns buildtest.app/Contents/Resources
|
||||||
|
- cp bin/testapp buildtest.app/Contents/MacOS
|
||||||
|
- cp build/Info.plist buildtest.app/Contents
|
35
v3/examples/build/build/Info.dev.plist
Normal file
35
v3/examples/build/build/Info.dev.plist
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>My App</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.app</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>v1.0.0</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>The ultimate thing</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>v1</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icons</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>(c) Me</string>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
||||||
|
</dict>
|
||||||
|
</plist>
|
27
v3/examples/build/build/Info.plist
Normal file
27
v3/examples/build/build/Info.plist
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>My App</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.app</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>v1.0.0</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>The ultimate thing</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>v1</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icons</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>(c) Me</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
BIN
v3/examples/build/build/appicon.png
Normal file
BIN
v3/examples/build/build/appicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
BIN
v3/examples/build/build/icons.icns
Normal file
BIN
v3/examples/build/build/icons.icns
Normal file
Binary file not shown.
BIN
v3/examples/build/build/icons.ico
Normal file
BIN
v3/examples/build/build/icons.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
15
v3/examples/build/build/info.json
Normal file
15
v3/examples/build/build/info.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"fixed": {
|
||||||
|
"file_version": "v1.0.0"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"0000": {
|
||||||
|
"ProductVersion": "v1.0.0",
|
||||||
|
"CompanyName": "My Company Name",
|
||||||
|
"FileDescription": "A thing that does a thing",
|
||||||
|
"LegalCopyright": "(c) 2023 My Company Name",
|
||||||
|
"ProductName": "My Product Name",
|
||||||
|
"Comments": "This is a comment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
v3/examples/build/build/wails.exe.manifest
Normal file
15
v3/examples/build/build/wails.exe.manifest
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity type="win32" name="com.wails.myproductname" version="v1.0.0.0" processorArchitecture="*"/>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
</assembly>
|
27
v3/examples/build/buildtest.app/Contents/Info.plist
Normal file
27
v3/examples/build/buildtest.app/Contents/Info.plist
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>My App</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.app</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>v1.0.0</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>The ultimate thing</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>v1</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icons</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>(c) Me</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
BIN
v3/examples/build/buildtest.app/Contents/Resources/icons.icns
Normal file
BIN
v3/examples/build/buildtest.app/Contents/Resources/icons.icns
Normal file
Binary file not shown.
275
v3/examples/build/main.go
Executable file
275
v3/examples/build/main.go
Executable file
@ -0,0 +1,275 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "WebviewWindow Demo",
|
||||||
|
Description: "A demo of the WebviewWindow API",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
app.On(events.Mac.ApplicationDidFinishLaunching, func() {
|
||||||
|
log.Println("ApplicationDidFinishLaunching")
|
||||||
|
})
|
||||||
|
|
||||||
|
currentWindow := func(fn func(window *application.WebviewWindow)) {
|
||||||
|
if app.CurrentWindow() != nil {
|
||||||
|
fn(app.CurrentWindow())
|
||||||
|
} else {
|
||||||
|
println("Current WebviewWindow is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a custom menu
|
||||||
|
menu := app.NewMenu()
|
||||||
|
menu.AddRole(application.AppMenu)
|
||||||
|
|
||||||
|
windowCounter := 1
|
||||||
|
|
||||||
|
// Let's make a "Demo" menu
|
||||||
|
myMenu := menu.AddSubmenu("New")
|
||||||
|
|
||||||
|
myMenu.Add("New WebviewWindow").
|
||||||
|
SetAccelerator("CmdOrCtrl+N").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindow().
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetURL("https://wails.io").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
myMenu.Add("New Frameless WebviewWindow").
|
||||||
|
SetAccelerator("CmdOrCtrl+F").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindow().
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetURL("https://wails.io").
|
||||||
|
SetFrameless(true).
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
myMenu.Add("New WebviewWindow (TitleBarHiddenInset)").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
TitleBar: options.TitleBarHiddenInset,
|
||||||
|
InvisibleTitleBarHeight: 25,
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetHTML("<br/><br/><p>A TitleBarHiddenInset WebviewWindow example</p>").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
myMenu.Add("New WebviewWindow (TitleBarHiddenInsetUnified)").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
TitleBar: options.TitleBarHiddenInsetUnified,
|
||||||
|
InvisibleTitleBarHeight: 50,
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetHTML("<br/><br/><p>A TitleBarHiddenInsetUnified WebviewWindow example</p>").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
myMenu.Add("New WebviewWindow (TitleBarHidden)").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
TitleBar: options.TitleBarHidden,
|
||||||
|
InvisibleTitleBarHeight: 25,
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetHTML("<br/><br/><p>A TitleBarHidden WebviewWindow example</p>").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeMenu := menu.AddSubmenu("Size")
|
||||||
|
sizeMenu.Add("Set Size (800,600)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetSize(800, 600)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sizeMenu.Add("Set Size (Random)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetSize(rand.Intn(800)+200, rand.Intn(600)+200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
sizeMenu.Add("Set Min Size (200,200)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetMinSize(200, 200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
sizeMenu.Add("Set Max Size (600,600)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetFullscreenButtonEnabled(false)
|
||||||
|
w.SetMaxSize(600, 600)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
width, height := w.Size()
|
||||||
|
app.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sizeMenu.Add("Reset Min Size").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetMinSize(0, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetMaxSize(0, 0)
|
||||||
|
w.SetFullscreenButtonEnabled(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
positionMenu := menu.AddSubmenu("Position")
|
||||||
|
positionMenu.Add("Set Position (0,0)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetPosition(0, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
positionMenu.Add("Set Position (Random)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetPosition(rand.Intn(1000), rand.Intn(800))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
positionMenu.Add("Get Position").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
x, y := w.Position()
|
||||||
|
app.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
positionMenu.Add("Center").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Center()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu := menu.AddSubmenu("State")
|
||||||
|
stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Minimise()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
w.Restore()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Maximise").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Maximise()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Fullscreen").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Fullscreen()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("UnFullscreen").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.UnFullscreen()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Restore").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Restore()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Hide (for 2 seconds)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Hide()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
w.Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Always on Top").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetAlwaysOnTop(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Not always on Top").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetAlwaysOnTop(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Google.com").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetURL("https://google.com")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("wails.io").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetURL("https://wails.io")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) {
|
||||||
|
screen, err := app.GetPrimaryScreen()
|
||||||
|
if err != nil {
|
||||||
|
app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("Screen: %+v", screen)
|
||||||
|
app.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show()
|
||||||
|
})
|
||||||
|
stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) {
|
||||||
|
screens, err := app.GetScreens()
|
||||||
|
if err != nil {
|
||||||
|
app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, screen := range screens {
|
||||||
|
msg := fmt.Sprintf("Screen: %+v", screen)
|
||||||
|
app.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
screen, err := w.GetScreen()
|
||||||
|
if err != nil {
|
||||||
|
app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("Screen: %+v", screen)
|
||||||
|
app.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
app.SetMenu(menu)
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
71
v3/examples/clipboard/main.go
Normal file
71
v3/examples/clipboard/main.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "Clipboard Demo",
|
||||||
|
Description: "A demo of the clipboard API",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a custom menu
|
||||||
|
menu := app.NewMenu()
|
||||||
|
menu.AddRole(application.AppMenu)
|
||||||
|
|
||||||
|
setClipboardMenu := menu.AddSubmenu("Set Clipboard")
|
||||||
|
setClipboardMenu.Add("Set Text 'Hello'").OnClick(func(ctx *application.Context) {
|
||||||
|
success := app.Clipboard().SetText("Hello")
|
||||||
|
if !success {
|
||||||
|
app.InfoDialog().SetMessage("Failed to set clipboard text").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setClipboardMenu.Add("Set Text 'World'").OnClick(func(ctx *application.Context) {
|
||||||
|
success := app.Clipboard().SetText("World")
|
||||||
|
if !success {
|
||||||
|
app.InfoDialog().SetMessage("Failed to set clipboard text").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setClipboardMenu.Add("Set Text (current time)").OnClick(func(ctx *application.Context) {
|
||||||
|
success := app.Clipboard().SetText(time.Now().String())
|
||||||
|
if !success {
|
||||||
|
app.InfoDialog().SetMessage("Failed to set clipboard text").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
getClipboardMenu := menu.AddSubmenu("Get Clipboard")
|
||||||
|
getClipboardMenu.Add("Get Text").OnClick(func(ctx *application.Context) {
|
||||||
|
result := app.Clipboard().Text()
|
||||||
|
app.InfoDialog().SetMessage("Got:\n\n" + result).Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
clearClipboardMenu := menu.AddSubmenu("Clear Clipboard")
|
||||||
|
clearClipboardMenu.Add("Clear Text").OnClick(func(ctx *application.Context) {
|
||||||
|
success := app.Clipboard().SetText("")
|
||||||
|
if success {
|
||||||
|
app.InfoDialog().SetMessage("Clipboard text cleared").Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("Clipboard text not cleared").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.SetMenu(menu)
|
||||||
|
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
335
v3/examples/dialogs/main.go
Normal file
335
v3/examples/dialogs/main.go
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "Dialogs Demo",
|
||||||
|
Description: "A demo of the dialogs API",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// Create a custom menu
|
||||||
|
menu := app.NewMenu()
|
||||||
|
menu.AddRole(application.AppMenu)
|
||||||
|
|
||||||
|
// Let's make a "Demo" menu
|
||||||
|
infoMenu := menu.AddSubmenu("Info")
|
||||||
|
infoMenu.Add("Info").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.InfoDialog()
|
||||||
|
dialog.SetTitle("Custom Title")
|
||||||
|
dialog.SetMessage("This is a custom message")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
infoMenu.Add("Info (Title only)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.InfoDialog()
|
||||||
|
dialog.SetTitle("Custom Title")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
infoMenu.Add("Info (Message only)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.InfoDialog()
|
||||||
|
dialog.SetMessage("This is a custom message")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
infoMenu.Add("Info (Custom Icon)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.InfoDialog()
|
||||||
|
dialog.SetTitle("Custom Icon Example")
|
||||||
|
dialog.SetMessage("Using a custom icon")
|
||||||
|
dialog.SetIcon(application.DefaultApplicationIcon)
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
questionMenu := menu.AddSubmenu("Question")
|
||||||
|
questionMenu.Add("Question (No default)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.QuestionDialog()
|
||||||
|
dialog.SetMessage("No default button")
|
||||||
|
dialog.AddButton("Yes")
|
||||||
|
dialog.AddButton("No")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
questionMenu.Add("Question (With Default)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.QuestionDialog()
|
||||||
|
dialog.SetTitle("Quit")
|
||||||
|
dialog.SetMessage("You have unsaved work. Are you sure you want to quit?")
|
||||||
|
dialog.AddButton("Yes").OnClick(func() {
|
||||||
|
app.Quit()
|
||||||
|
})
|
||||||
|
no := dialog.AddButton("No")
|
||||||
|
dialog.SetDefaultButton(no)
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
questionMenu.Add("Question (With Cancel)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.QuestionDialog().
|
||||||
|
SetTitle("Update").
|
||||||
|
SetMessage("The cancel button is selected when pressing escape")
|
||||||
|
download := dialog.AddButton("📥 Download")
|
||||||
|
download.OnClick(func() {
|
||||||
|
app.InfoDialog().SetMessage("Downloading...").Show()
|
||||||
|
})
|
||||||
|
no := dialog.AddButton("Cancel")
|
||||||
|
dialog.SetDefaultButton(download)
|
||||||
|
dialog.SetCancelButton(no)
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
questionMenu.Add("Question (Custom Icon)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.QuestionDialog()
|
||||||
|
dialog.SetTitle("Custom Icon Example")
|
||||||
|
dialog.SetMessage("Using a custom icon")
|
||||||
|
dialog.SetIcon(application.WailsLogoWhiteTransparent)
|
||||||
|
dialog.SetDefaultButton(dialog.AddButton("I like it!"))
|
||||||
|
dialog.AddButton("Not so keen...")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
warningMenu := menu.AddSubmenu("Warning")
|
||||||
|
warningMenu.Add("Warning").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.WarningDialog()
|
||||||
|
dialog.SetTitle("Custom Title")
|
||||||
|
dialog.SetMessage("This is a custom message")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
warningMenu.Add("Warning (Title only)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.WarningDialog()
|
||||||
|
dialog.SetTitle("Custom Title")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
warningMenu.Add("Warning (Message only)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.WarningDialog()
|
||||||
|
dialog.SetMessage("This is a custom message")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
warningMenu.Add("Warning (Custom Icon)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.WarningDialog()
|
||||||
|
dialog.SetTitle("Custom Icon Example")
|
||||||
|
dialog.SetMessage("Using a custom icon")
|
||||||
|
dialog.SetIcon(application.DefaultApplicationIcon)
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
errorMenu := menu.AddSubmenu("Error")
|
||||||
|
errorMenu.Add("Error").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.ErrorDialog()
|
||||||
|
dialog.SetTitle("Ooops")
|
||||||
|
dialog.SetMessage("I accidentally the whole of Twitter")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
errorMenu.Add("Error (Title Only)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.ErrorDialog()
|
||||||
|
dialog.SetTitle("Custom Title")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
errorMenu.Add("Error (Custom Message)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.ErrorDialog()
|
||||||
|
dialog.SetMessage("This is a custom message")
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
errorMenu.Add("Error (Custom Icon)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.ErrorDialog()
|
||||||
|
dialog.SetTitle("Custom Icon Example")
|
||||||
|
dialog.SetMessage("Using a custom icon")
|
||||||
|
dialog.SetIcon(application.WailsLogoWhite)
|
||||||
|
dialog.Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
openMenu := menu.AddSubmenu("Open")
|
||||||
|
openMenu.Add("Open File").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No file selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open File (Show Hidden Files)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No file selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open File (Attach to window)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
AttachToWindow(app.CurrentWindow()).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No file selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open Multiple Files (Show Hidden Files)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
PromptForMultipleSelection()
|
||||||
|
if len(result) > 0 {
|
||||||
|
app.InfoDialog().SetMessage(strings.Join(result, ",")).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No file selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open Directory").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.OpenFileDialog().
|
||||||
|
CanChooseDirectories(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No directory selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open Directory (Create Directories)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.OpenFileDialog().
|
||||||
|
CanChooseDirectories(true).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No directory selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open Directory (Resolves Aliases)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.OpenFileDialog().
|
||||||
|
CanChooseDirectories(true).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
ResolvesAliases(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No directory selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open File/Directory (Set Title)").OnClick(func(ctx *application.Context) {
|
||||||
|
dialog := app.OpenFileDialog().
|
||||||
|
CanChooseDirectories(true).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
ResolvesAliases(true)
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
dialog.SetMessage("Select a file/directory")
|
||||||
|
} else {
|
||||||
|
dialog.SetTitle("Select a file/directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := dialog.PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No file/directory selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openMenu.Add("Open (Full Example)").OnClick(func(ctx *application.Context) {
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
dialog := app.OpenFileDialog().
|
||||||
|
SetTitle("Select a file").
|
||||||
|
SetMessage("Select a file to open").
|
||||||
|
SetButtonText("Let's do this!").
|
||||||
|
SetDirectory(cwd).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
ResolvesAliases(true).
|
||||||
|
AllowsOtherFileTypes(true).
|
||||||
|
TreatsFilePackagesAsDirectories(true).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
CanSelectHiddenExtension(true).
|
||||||
|
AddFilter("Text Files", "*.txt; *.md").
|
||||||
|
AddFilter("Video Files", "*.mov; *.mp4; *.avi")
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
dialog.SetMessage("Select a file")
|
||||||
|
} else {
|
||||||
|
dialog.SetTitle("Select a file")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := dialog.PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
} else {
|
||||||
|
app.InfoDialog().SetMessage("No file selected").Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
saveMenu := menu.AddSubmenu("Save")
|
||||||
|
saveMenu.Add("Select File (Defaults)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.SaveFileDialog().
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveMenu.Add("Select File (Attach To WebviewWindow)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.SaveFileDialog().
|
||||||
|
AttachToWindow(app.CurrentWindow()).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveMenu.Add("Select File (Show Hidden Files)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.SaveFileDialog().
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveMenu.Add("Select File (Cannot Create Directories)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.SaveFileDialog().
|
||||||
|
CanCreateDirectories(false).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveMenu.Add("Select File (Full Example)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, _ := app.SaveFileDialog().
|
||||||
|
CanCreateDirectories(false).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
SetMessage("Select a file").
|
||||||
|
SetDirectory("/Applications").
|
||||||
|
SetButtonText("Let's do this!").
|
||||||
|
SetFilename("README.md").
|
||||||
|
HideExtension(true).
|
||||||
|
AllowsOtherFileTypes(true).
|
||||||
|
TreatsFilePackagesAsDirectories(true).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if result != "" {
|
||||||
|
app.InfoDialog().SetMessage(result).Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.SetMenu(menu)
|
||||||
|
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
225
v3/examples/kitchensink/main.go
Normal file
225
v3/examples/kitchensink/main.go
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "Menu Demo",
|
||||||
|
Description: "A demo of the menu system",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
app.On(events.Mac.ApplicationDidFinishLaunching, func() {
|
||||||
|
println("ApplicationDidFinishLaunching")
|
||||||
|
})
|
||||||
|
app.On(events.Mac.ApplicationWillTerminate, func() {
|
||||||
|
println("ApplicationWillTerminate")
|
||||||
|
})
|
||||||
|
app.On(events.Mac.ApplicationDidBecomeActive, func() {
|
||||||
|
println("ApplicationDidBecomeActive")
|
||||||
|
})
|
||||||
|
app.On(events.Mac.ApplicationDidChangeBackingProperties, func() {
|
||||||
|
println("ApplicationDidChangeBackingProperties")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.On(events.Mac.ApplicationDidChangeEffectiveAppearance, func() {
|
||||||
|
println("ApplicationDidChangeEffectiveAppearance")
|
||||||
|
})
|
||||||
|
app.On(events.Mac.ApplicationDidHide, func() {
|
||||||
|
println("ApplicationDidHide")
|
||||||
|
})
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
menuCallback := func(ctx *application.Context) {
|
||||||
|
menuItem := ctx.ClickedMenuItem()
|
||||||
|
menuItem.SetLabel("Clicked!")
|
||||||
|
}
|
||||||
|
|
||||||
|
radioCallback := func(ctx *application.Context) {
|
||||||
|
menuItem := ctx.ClickedMenuItem()
|
||||||
|
menuItem.SetLabel(menuItem.Label() + "!")
|
||||||
|
}
|
||||||
|
|
||||||
|
myMenu := app.NewMenu()
|
||||||
|
file1 := myMenu.Add("File")
|
||||||
|
file1.SetTooltip("Create New Tray Menu")
|
||||||
|
file1.OnClick(menuCallback)
|
||||||
|
myMenu.Add("Create New Tray Menu").
|
||||||
|
SetAccelerator("CmdOrCtrl+N").
|
||||||
|
SetTooltip("ROFLCOPTER!!!!").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
mySystray := app.NewSystemTray()
|
||||||
|
mySystray.SetLabel("Wails")
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
mySystray.SetTemplateIcon(application.DefaultMacTemplateIcon)
|
||||||
|
} else {
|
||||||
|
mySystray.SetIcon(application.DefaultApplicationIcon)
|
||||||
|
}
|
||||||
|
myMenu := app.NewMenu()
|
||||||
|
myMenu.Add("Item 1")
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
myMenu.Add("Kill this menu").OnClick(func(ctx *application.Context) {
|
||||||
|
mySystray.Destroy()
|
||||||
|
})
|
||||||
|
mySystray.SetMenu(myMenu)
|
||||||
|
|
||||||
|
})
|
||||||
|
myMenu.Add("Not Enabled").SetEnabled(false)
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
myMenu.AddCheckbox("My checkbox", true).OnClick(menuCallback)
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
myMenu.AddRadio("Radio 1", true).OnClick(radioCallback)
|
||||||
|
myMenu.AddRadio("Radio 2", false).OnClick(radioCallback)
|
||||||
|
myMenu.AddRadio("Radio 3", false).OnClick(radioCallback)
|
||||||
|
|
||||||
|
submenu := myMenu.AddSubmenu("Submenu")
|
||||||
|
submenu.Add("Submenu item 1").OnClick(menuCallback)
|
||||||
|
submenu.Add("Submenu item 2").OnClick(menuCallback)
|
||||||
|
submenu.Add("Submenu item 3").OnClick(menuCallback)
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
file4 := myMenu.Add("File 4").OnClick(func(*application.Context) {
|
||||||
|
println("File 4 clicked")
|
||||||
|
})
|
||||||
|
|
||||||
|
myMenu.Add("Click to toggle").OnClick(func(*application.Context) {
|
||||||
|
enabled := file4.Enabled()
|
||||||
|
println("Enabled: ", enabled)
|
||||||
|
file4.SetEnabled(!enabled)
|
||||||
|
})
|
||||||
|
myMenu.Add("File 5").OnClick(menuCallback)
|
||||||
|
|
||||||
|
mySystray := app.NewSystemTray()
|
||||||
|
mySystray.SetLabel("Wails is awesome")
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
mySystray.SetTemplateIcon(application.DefaultMacTemplateIcon)
|
||||||
|
} else {
|
||||||
|
mySystray.SetIcon(application.DefaultApplicationIcon)
|
||||||
|
}
|
||||||
|
mySystray.SetMenu(myMenu)
|
||||||
|
mySystray.SetIconPosition(application.NSImageLeading)
|
||||||
|
|
||||||
|
myWindow := app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Title: "Kitchen Sink",
|
||||||
|
Width: 600,
|
||||||
|
Height: 400,
|
||||||
|
AlwaysOnTop: true,
|
||||||
|
DisableResize: false,
|
||||||
|
BackgroundColour: &options.RGBA{
|
||||||
|
Red: 255,
|
||||||
|
Green: 255,
|
||||||
|
Blue: 255,
|
||||||
|
Alpha: 30,
|
||||||
|
},
|
||||||
|
StartState: options.WindowStateMaximised,
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
Backdrop: options.MacBackdropTranslucent,
|
||||||
|
Appearance: options.NSAppearanceNameDarkAqua,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
myWindow.On(events.Mac.WindowWillClose, func() {
|
||||||
|
println(myWindow.ID(), "WindowWillClose")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidResize, func() {
|
||||||
|
//w, h := myWindow.Size()
|
||||||
|
//println(myWindow.ID(), "WindowDidResize", w, h)
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidMove, func() {
|
||||||
|
//x, y := myWindow.Position()
|
||||||
|
//println(myWindow.ID(), "WindowDidMove", x, y)
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidMiniaturize, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidMiniaturize")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidDeminiaturize, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidDeminiaturize")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidBecomeKey, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidBecomeKey")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidResignKey, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidResignKey")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidBecomeMain, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidBecomeMain")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidResignMain, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidResignMain")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowWillEnterFullScreen, func() {
|
||||||
|
println(myWindow.ID(), "WindowWillEnterFullScreen")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidEnterFullScreen, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidEnterFullScreen")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowWillExitFullScreen, func() {
|
||||||
|
println(myWindow.ID(), "WindowWillExitFullScreen")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidExitFullScreen, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidExitFullScreen")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowWillEnterVersionBrowser, func() {
|
||||||
|
println(myWindow.ID(), "WindowWillEnterVersionBrowser")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidEnterVersionBrowser, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidEnterVersionBrowser")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowWillExitVersionBrowser, func() {
|
||||||
|
println(myWindow.ID(), "WindowWillExitVersionBrowser")
|
||||||
|
})
|
||||||
|
myWindow.On(events.Mac.WindowDidExitVersionBrowser, func() {
|
||||||
|
println(myWindow.ID(), "WindowDidExitVersionBrowser")
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
var myWindow2 *application.WebviewWindow
|
||||||
|
var myWindow2Lock sync.RWMutex
|
||||||
|
myWindow2 = app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Title: "#2",
|
||||||
|
Width: 1024,
|
||||||
|
Height: 768,
|
||||||
|
AlwaysOnTop: false,
|
||||||
|
URL: "https://google.com",
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
Backdrop: options.MacBackdropTranslucent,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
//myWindow2.On(events.Mac.WindowDidMove, func() {
|
||||||
|
// myWindow2Lock.RLock()
|
||||||
|
// x, y := myWindow2.Position()
|
||||||
|
// println(myWindow2.ID(), "WindowDidMove: ", x, y)
|
||||||
|
// myWindow2Lock.RUnlock()
|
||||||
|
//})
|
||||||
|
//
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
myWindow2Lock.RLock()
|
||||||
|
myWindow.SetTitle("Wooooo")
|
||||||
|
myWindow.SetAlwaysOnTop(true)
|
||||||
|
myWindow2.SetTitle("OMG")
|
||||||
|
myWindow2.SetURL("https://wails.io")
|
||||||
|
myWindow.SetMinSize(600, 600)
|
||||||
|
myWindow.SetMaxSize(650, 650)
|
||||||
|
myWindow.Center()
|
||||||
|
myWindow2Lock.RUnlock()
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
97
v3/examples/menu/main.go
Normal file
97
v3/examples/menu/main.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "Menu Demo",
|
||||||
|
Description: "A demo of the menu system",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a custom menu
|
||||||
|
menu := app.NewMenu()
|
||||||
|
menu.AddRole(application.AppMenu)
|
||||||
|
|
||||||
|
// Let's make a "Demo" menu
|
||||||
|
myMenu := menu.AddSubmenu("Demo")
|
||||||
|
|
||||||
|
// Disabled menu item
|
||||||
|
myMenu.Add("Not Enabled").SetEnabled(false)
|
||||||
|
|
||||||
|
// Click callbacks
|
||||||
|
myMenu.Add("Click Me!").OnClick(func(ctx *application.Context) {
|
||||||
|
ctx.ClickedMenuItem().SetLabel("Thanks mate!")
|
||||||
|
})
|
||||||
|
|
||||||
|
// You can control the current window from the menu
|
||||||
|
myMenu.Add("Lock WebviewWindow Resize").OnClick(func(ctx *application.Context) {
|
||||||
|
if app.CurrentWindow().Resizable() {
|
||||||
|
app.CurrentWindow().SetResizable(false)
|
||||||
|
ctx.ClickedMenuItem().SetLabel("Unlock WebviewWindow Resize")
|
||||||
|
} else {
|
||||||
|
app.CurrentWindow().SetResizable(true)
|
||||||
|
ctx.ClickedMenuItem().SetLabel("Lock WebviewWindow Resize")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
|
||||||
|
// Checkboxes will tell you their new state so you don't need to track it
|
||||||
|
myMenu.AddCheckbox("My checkbox", true).OnClick(func(context *application.Context) {
|
||||||
|
println("Clicked checkbox. Checked:", context.ClickedMenuItem().Checked())
|
||||||
|
})
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
|
||||||
|
// Callbacks can be shared. This is useful for radio groups
|
||||||
|
radioCallback := func(ctx *application.Context) {
|
||||||
|
menuItem := ctx.ClickedMenuItem()
|
||||||
|
menuItem.SetLabel(menuItem.Label() + "!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radio groups are created implicitly by placing radio items next to each other in a menu
|
||||||
|
myMenu.AddRadio("Radio 1", true).OnClick(radioCallback)
|
||||||
|
myMenu.AddRadio("Radio 2", false).OnClick(radioCallback)
|
||||||
|
myMenu.AddRadio("Radio 3", false).OnClick(radioCallback)
|
||||||
|
|
||||||
|
// Submenus are also supported
|
||||||
|
submenu := myMenu.AddSubmenu("Submenu")
|
||||||
|
submenu.Add("Submenu item 1")
|
||||||
|
submenu.Add("Submenu item 2")
|
||||||
|
submenu.Add("Submenu item 3")
|
||||||
|
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
|
||||||
|
beatles := myMenu.Add("Hello").OnClick(func(*application.Context) {
|
||||||
|
println("The beatles would be proud")
|
||||||
|
})
|
||||||
|
myMenu.Add("Toggle the menuitem above").OnClick(func(*application.Context) {
|
||||||
|
if beatles.Enabled() {
|
||||||
|
beatles.SetEnabled(false)
|
||||||
|
beatles.SetLabel("Goodbye")
|
||||||
|
} else {
|
||||||
|
beatles.SetEnabled(true)
|
||||||
|
beatles.SetLabel("Hello")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.SetMenu(menu)
|
||||||
|
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
45
v3/examples/plain/main.go
Normal file
45
v3/examples/plain/main.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "Plain",
|
||||||
|
Description: "A demo of using raw HTML & CSS",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// Create window
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Title: "Plain Bundle",
|
||||||
|
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
InvisibleTitleBarHeight: 50,
|
||||||
|
Backdrop: options.MacBackdropTranslucent,
|
||||||
|
TitleBar: options.TitleBarHiddenInset,
|
||||||
|
},
|
||||||
|
|
||||||
|
URL: "/",
|
||||||
|
Assets: options.Assets{
|
||||||
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`<html><head><title>Plain Bundle</title></head><body><div class="main"><h1>Plain Bundle</h1><p>This is a plain bundle. It has no frontend code but this was Served by the AssetServer's Handler</p></div></body></html>`))
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
47
v3/examples/systray/main.go
Normal file
47
v3/examples/systray/main.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "Systray Demo",
|
||||||
|
Description: "A demo of the Systray API",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ActivationPolicy: options.ActivationPolicyAccessory,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
systemTray := app.NewSystemTray()
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
systemTray.SetIcon(application.DefaultMacTemplateIcon)
|
||||||
|
}
|
||||||
|
|
||||||
|
myMenu := app.NewMenu()
|
||||||
|
myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) {
|
||||||
|
app.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show()
|
||||||
|
})
|
||||||
|
subMenu := myMenu.AddSubmenu("Submenu")
|
||||||
|
subMenu.Add("Click me!").OnClick(func(ctx *application.Context) {
|
||||||
|
ctx.ClickedMenuItem().SetLabel("Clicked!")
|
||||||
|
})
|
||||||
|
myMenu.AddSeparator()
|
||||||
|
myMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||||
|
app.Quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
systemTray.SetMenu(myMenu)
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
277
v3/examples/window/main.go
Normal file
277
v3/examples/window/main.go
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Name: "WebviewWindow Demo",
|
||||||
|
Description: "A demo of the WebviewWindow API",
|
||||||
|
Mac: options.Mac{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
app.On(events.Mac.ApplicationDidFinishLaunching, func() {
|
||||||
|
log.Println("ApplicationDidFinishLaunching")
|
||||||
|
})
|
||||||
|
|
||||||
|
currentWindow := func(fn func(window *application.WebviewWindow)) {
|
||||||
|
if app.CurrentWindow() != nil {
|
||||||
|
fn(app.CurrentWindow())
|
||||||
|
} else {
|
||||||
|
println("Current WebviewWindow is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a custom menu
|
||||||
|
menu := app.NewMenu()
|
||||||
|
menu.AddRole(application.AppMenu)
|
||||||
|
|
||||||
|
windowCounter := 1
|
||||||
|
|
||||||
|
// Let's make a "Demo" menu
|
||||||
|
myMenu := menu.AddSubmenu("New")
|
||||||
|
|
||||||
|
myMenu.Add("New WebviewWindow").
|
||||||
|
SetAccelerator("CmdOrCtrl+N").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindow().
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetURL("https://wails.io").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
myMenu.Add("New Frameless WebviewWindow").
|
||||||
|
SetAccelerator("CmdOrCtrl+F").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
X: rand.Intn(1000),
|
||||||
|
Y: rand.Intn(800),
|
||||||
|
Frameless: true,
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
InvisibleTitleBarHeight: 50,
|
||||||
|
},
|
||||||
|
}).Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
myMenu.Add("New WebviewWindow (TitleBarHiddenInset)").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
TitleBar: options.TitleBarHiddenInset,
|
||||||
|
InvisibleTitleBarHeight: 25,
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetHTML("<br/><br/><p>A TitleBarHiddenInset WebviewWindow example</p>").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
myMenu.Add("New WebviewWindow (TitleBarHiddenInsetUnified)").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
TitleBar: options.TitleBarHiddenInsetUnified,
|
||||||
|
InvisibleTitleBarHeight: 50,
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetHTML("<br/><br/><p>A TitleBarHiddenInsetUnified WebviewWindow example</p>").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
myMenu.Add("New WebviewWindow (TitleBarHidden)").
|
||||||
|
OnClick(func(ctx *application.Context) {
|
||||||
|
app.NewWebviewWindowWithOptions(&options.WebviewWindow{
|
||||||
|
Mac: options.MacWindow{
|
||||||
|
TitleBar: options.TitleBarHidden,
|
||||||
|
InvisibleTitleBarHeight: 25,
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||||
|
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||||
|
SetHTML("<br/><br/><p>A TitleBarHidden WebviewWindow example</p>").
|
||||||
|
Show()
|
||||||
|
windowCounter++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeMenu := menu.AddSubmenu("Size")
|
||||||
|
sizeMenu.Add("Set Size (800,600)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetSize(800, 600)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sizeMenu.Add("Set Size (Random)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetSize(rand.Intn(800)+200, rand.Intn(600)+200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
sizeMenu.Add("Set Min Size (200,200)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetMinSize(200, 200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
sizeMenu.Add("Set Max Size (600,600)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetFullscreenButtonEnabled(false)
|
||||||
|
w.SetMaxSize(600, 600)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
width, height := w.Size()
|
||||||
|
app.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sizeMenu.Add("Reset Min Size").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetMinSize(0, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetMaxSize(0, 0)
|
||||||
|
w.SetFullscreenButtonEnabled(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
positionMenu := menu.AddSubmenu("Position")
|
||||||
|
positionMenu.Add("Set Position (0,0)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetPosition(0, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
positionMenu.Add("Set Position (Random)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetPosition(rand.Intn(1000), rand.Intn(800))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
positionMenu.Add("Get Position").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
x, y := w.Position()
|
||||||
|
app.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
positionMenu.Add("Center").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Center()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu := menu.AddSubmenu("State")
|
||||||
|
stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Minimise()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
w.Restore()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Maximise").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Maximise()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Fullscreen").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Fullscreen()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("UnFullscreen").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.UnFullscreen()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Restore").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Restore()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Hide (for 2 seconds)").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.Hide()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
w.Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Always on Top").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetAlwaysOnTop(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Not always on Top").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetAlwaysOnTop(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Google.com").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetURL("https://google.com")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("wails.io").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
w.SetURL("https://wails.io")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) {
|
||||||
|
screen, err := app.GetPrimaryScreen()
|
||||||
|
if err != nil {
|
||||||
|
app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("Screen: %+v", screen)
|
||||||
|
app.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show()
|
||||||
|
})
|
||||||
|
stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) {
|
||||||
|
screens, err := app.GetScreens()
|
||||||
|
if err != nil {
|
||||||
|
app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, screen := range screens {
|
||||||
|
msg := fmt.Sprintf("Screen: %+v", screen)
|
||||||
|
app.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) {
|
||||||
|
currentWindow(func(w *application.WebviewWindow) {
|
||||||
|
screen, err := w.GetScreen()
|
||||||
|
if err != nil {
|
||||||
|
app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("Screen: %+v", screen)
|
||||||
|
app.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
app.SetMenu(menu)
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
v3/go.mod
Normal file
56
v3/go.mod
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
module github.com/wailsapp/wails/v3
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-task/task/v3 v3.19.1
|
||||||
|
github.com/jackmordaunt/icns/v2 v2.2.1
|
||||||
|
github.com/leaanthony/clir v1.5.0
|
||||||
|
github.com/leaanthony/winicon v1.0.0
|
||||||
|
github.com/pterm/pterm v0.12.51
|
||||||
|
github.com/samber/lo v1.37.0
|
||||||
|
github.com/stretchr/testify v1.8.1
|
||||||
|
github.com/tc-hib/winres v0.1.6
|
||||||
|
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6
|
||||||
|
golang.org/x/tools v0.1.12
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
atomicgo.dev/cursor v0.1.1 // indirect
|
||||||
|
atomicgo.dev/keyboard v0.2.8 // indirect
|
||||||
|
github.com/containerd/console v1.0.3 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/fatih/color v1.13.0 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
|
github.com/gookit/color v1.5.2 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
|
github.com/joho/godotenv v1.4.0 // indirect
|
||||||
|
github.com/leaanthony/slicer v1.5.0 // indirect
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.5 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/mattn/go-zglob v0.0.4 // indirect
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/radovskyb/watcher v1.0.7 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/sajari/fuzzy v1.0.0 // indirect
|
||||||
|
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||||
|
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
|
golang.org/x/sys v0.3.0 // indirect
|
||||||
|
golang.org/x/term v0.3.0 // indirect
|
||||||
|
golang.org/x/text v0.4.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
mvdan.cc/sh/v3 v3.6.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/go-task/task/v3 v3.19.1 => github.com/wailsapp/task/v3 v3.19.1
|
179
v3/go.sum
Normal file
179
v3/go.sum
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=
|
||||||
|
atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4=
|
||||||
|
atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
|
||||||
|
atomicgo.dev/keyboard v0.2.8 h1:Di09BitwZgdTV1hPyX/b9Cqxi8HVuJQwWivnZUEqlj4=
|
||||||
|
atomicgo.dev/keyboard v0.2.8/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
|
||||||
|
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
|
||||||
|
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
|
||||||
|
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||||
|
github.com/MarvinJWendt/testza v0.5.1 h1:a9Fqx6vQrHQ4CyiaLhktfTTelwGotmFWy8MNhyaohw8=
|
||||||
|
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||||
|
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||||
|
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||||
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
|
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
|
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||||
|
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
|
||||||
|
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
|
||||||
|
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
|
||||||
|
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||||
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
|
github.com/jackmordaunt/icns/v2 v2.2.1 h1:MGklwYP2yohKn2Bw7XxlF69LZe98S1vUfl5OvAulPwg=
|
||||||
|
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
|
||||||
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.0 h1:4ZexSFt8agMNzNisrsilL6RClWDC5YJnLHNIfTy4iuc=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||||
|
github.com/leaanthony/clir v1.5.0 h1:zaH7fgsZ5OLfr0YwJBwQ+EYxCjXQsHF+CRudIiZb0KQ=
|
||||||
|
github.com/leaanthony/clir v1.5.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||||
|
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
||||||
|
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||||
|
github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ=
|
||||||
|
github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c=
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
|
||||||
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||||
|
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM=
|
||||||
|
github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||||
|
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
|
||||||
|
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
|
||||||
|
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
|
||||||
|
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||||
|
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||||
|
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
||||||
|
github.com/pterm/pterm v0.12.51 h1:iwhNG1FhQMgks+5kVyr/ClRk3WJCuL907nJN7RqmEpw=
|
||||||
|
github.com/pterm/pterm v0.12.51/go.mod h1:79BLm4vos2z+eOoHnDG7ZWuYtLaSStyaspKjGmSoxc4=
|
||||||
|
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||||
|
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||||
|
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||||
|
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||||
|
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
|
||||||
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||||
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/tc-hib/winres v0.1.6 h1:qgsYHze+BxQPEYilxIz/KCQGaClvI2+yLBAZs+3+0B8=
|
||||||
|
github.com/tc-hib/winres v0.1.6/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A=
|
||||||
|
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||||
|
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||||
|
github.com/wailsapp/task/v3 v3.19.1 h1:syDKYaPBXgrXKKSJVEWcOEoSFtZzpvxqlHf90YRukRc=
|
||||||
|
github.com/wailsapp/task/v3 v3.19.1/go.mod h1:y7rWakbLR5gFElGgo6rA2dyr6vU/zNIDVfn3S4Of6OI=
|
||||||
|
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 h1:Wn+nhnS+VytzE0PegUzSh4T3hXJCtggKGD/4U5H9+wQ=
|
||||||
|
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||||
|
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
|
||||||
|
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||||
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
mvdan.cc/sh/v3 v3.6.0 h1:gtva4EXJ0dFNvl5bHjcUEvws+KRcDslT8VKheTYkbGU=
|
||||||
|
mvdan.cc/sh/v3 v3.6.0/go.mod h1:U4mhtBLZ32iWhif5/lD+ygy1zrgaQhUu+XFy7C8+TTA=
|
42
v3/internal/commands/bindings.go
Normal file
42
v3/internal/commands/bindings.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/internal/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenerateBindingsOptions struct {
|
||||||
|
ModelsFilename string `name:"m" description:"The filename for the models file" default:"models.ts"`
|
||||||
|
BindingsFilename string `name:"b" description:"The filename for the bindings file" default:"bindings.js"`
|
||||||
|
ProjectDirectory string `name:"d" description:"The project directory" default:"."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateBindings(options *GenerateBindingsOptions) error {
|
||||||
|
|
||||||
|
parserContext, err := parser.ParseDirectory(options.ProjectDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate models
|
||||||
|
modelsData, err := parser.GenerateModels(parserContext)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error generating models: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelsFileData bytes.Buffer
|
||||||
|
modelsFileData.WriteString(`// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
`)
|
||||||
|
modelsFileData.Write(modelsData)
|
||||||
|
err = os.WriteFile(options.ModelsFilename, modelsFileData.Bytes(), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing models file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
61
v3/internal/commands/defaults.go
Normal file
61
v3/internal/commands/defaults.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed defaults/info.json
|
||||||
|
var Info []byte
|
||||||
|
|
||||||
|
//go:embed defaults/wails.exe.manifest
|
||||||
|
var Manifest []byte
|
||||||
|
|
||||||
|
//go:embed defaults/appicon.png
|
||||||
|
var AppIcon []byte
|
||||||
|
|
||||||
|
//go:embed defaults/icons.ico
|
||||||
|
var IconsIco []byte
|
||||||
|
|
||||||
|
//go:embed defaults/Info.plist
|
||||||
|
var InfoPlist []byte
|
||||||
|
|
||||||
|
//go:embed defaults/Info.dev.plist
|
||||||
|
var InfoDevPlist []byte
|
||||||
|
|
||||||
|
//go:embed defaults/icons.icns
|
||||||
|
var IconsIcns []byte
|
||||||
|
|
||||||
|
var AllAssets = map[string][]byte{
|
||||||
|
"info.json": Info,
|
||||||
|
"wails.exe.manifest": Manifest,
|
||||||
|
"appicon.png": AppIcon,
|
||||||
|
"icons.ico": IconsIco,
|
||||||
|
"Info.plist": InfoPlist,
|
||||||
|
"Info.dev.plist": InfoDevPlist,
|
||||||
|
"icons.icns": IconsIcns,
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultsOptions struct {
|
||||||
|
Dir string `description:"The directory to generate the files into"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Defaults(options *DefaultsOptions) error {
|
||||||
|
dir := options.Dir
|
||||||
|
if dir == "" {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
for filename, data := range AllAssets {
|
||||||
|
// If file exists, skip it
|
||||||
|
if _, err := os.Stat(dir + "/" + filename); err == nil {
|
||||||
|
println("Skipping " + filename)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := os.WriteFile(dir+"/"+filename, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
println("Generated " + filename)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
32
v3/internal/commands/defaults/Info.dev.plist
Normal file
32
v3/internal/commands/defaults/Info.dev.plist
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>{{.Info.ProductName}}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.{{.Name}}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{{.Info.Comments}}</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>iconfile</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
27
v3/internal/commands/defaults/Info.plist
Normal file
27
v3/internal/commands/defaults/Info.plist
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>{{.Info.ProductName}}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.{{.Name}}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{{.Info.Comments}}</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>iconfile</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
BIN
v3/internal/commands/defaults/appicon.png
Normal file
BIN
v3/internal/commands/defaults/appicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
BIN
v3/internal/commands/defaults/icons.icns
Normal file
BIN
v3/internal/commands/defaults/icons.icns
Normal file
Binary file not shown.
BIN
v3/internal/commands/defaults/icons.ico
Normal file
BIN
v3/internal/commands/defaults/icons.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
15
v3/internal/commands/defaults/info.json
Normal file
15
v3/internal/commands/defaults/info.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"fixed": {
|
||||||
|
"file_version": "v1.0.0"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"0000": {
|
||||||
|
"ProductVersion": "v1.0.0",
|
||||||
|
"CompanyName": "My Company Name",
|
||||||
|
"FileDescription": "A thing that does a thing",
|
||||||
|
"LegalCopyright": "(c) 2023 My Company Name",
|
||||||
|
"ProductName": "My Product Name",
|
||||||
|
"Comments": "This is a comment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
v3/internal/commands/defaults/wails.exe.manifest
Normal file
15
v3/internal/commands/defaults/wails.exe.manifest
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity type="win32" name="com.wails.myproductname" version="v1.0.0.0" processorArchitecture="*"/>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
</assembly>
|
135
v3/internal/commands/icons.go
Normal file
135
v3/internal/commands/icons.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jackmordaunt/icns/v2"
|
||||||
|
"github.com/leaanthony/winicon"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IconsOptions struct {
|
||||||
|
Example bool `description:"Generate example icon file (appicon.png) in the current directory"`
|
||||||
|
Input string `description:"The input image file"`
|
||||||
|
Sizes string `description:"The sizes to generate in .ico file (comma separated)"`
|
||||||
|
WindowsFilename string `description:"The output filename for the Windows icon"`
|
||||||
|
MacFilename string `description:"The output filename for the Mac icon bundle"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IconsOptions) Default() *IconsOptions {
|
||||||
|
return &IconsOptions{
|
||||||
|
Sizes: "256,128,64,48,32,16",
|
||||||
|
MacFilename: "icons.icns",
|
||||||
|
WindowsFilename: "icons.ico",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateIcons(options *IconsOptions) error {
|
||||||
|
|
||||||
|
if options.Example {
|
||||||
|
return generateExampleIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Input == "" {
|
||||||
|
return fmt.Errorf("input is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.WindowsFilename == "" && options.MacFilename == "" {
|
||||||
|
return fmt.Errorf("at least one output filename is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse sizes
|
||||||
|
var sizes = []int{256, 128, 64, 48, 32, 16}
|
||||||
|
var err error
|
||||||
|
if options.Sizes != "" {
|
||||||
|
sizes, err = parseSizes(options.Sizes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iconData, err := os.ReadFile(options.Input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.WindowsFilename != "" {
|
||||||
|
err := generateWindowsIcon(iconData, sizes, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.MacFilename != "" {
|
||||||
|
err := generateMacIcon(iconData, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateExampleIcon() error {
|
||||||
|
return os.WriteFile("appicon.png", []byte(AppIcon), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSizes(sizes string) ([]int, error) {
|
||||||
|
// split the input string by comma and confirm that each one is an integer
|
||||||
|
parsedSizes := strings.Split(sizes, ",")
|
||||||
|
var result []int
|
||||||
|
for _, size := range parsedSizes {
|
||||||
|
s, err := strconv.Atoi(size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// put all integers in a slice and return
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateMacIcon(iconData []byte, options *IconsOptions) error {
|
||||||
|
|
||||||
|
srcImg, _, err := image.Decode(bytes.NewBuffer(iconData))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dest, err := os.Create(options.MacFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = dest.Close()
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return icns.Encode(dest, srcImg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateWindowsIcon(iconData []byte, sizes []int, options *IconsOptions) error {
|
||||||
|
|
||||||
|
var output bytes.Buffer
|
||||||
|
|
||||||
|
err := winicon.GenerateIcon(bytes.NewBuffer(iconData), &output, sizes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(options.WindowsFilename, output.Bytes(), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
285
v3/internal/commands/icons_test.go
Normal file
285
v3/internal/commands/icons_test.go
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateIcon(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func() *IconsOptions
|
||||||
|
wantErr bool
|
||||||
|
test func() error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should generate an icon when using the `example` flag",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
return &IconsOptions{
|
||||||
|
Example: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
test: func() error {
|
||||||
|
// the file `appicon.png` should be created in the current directory
|
||||||
|
// check for the existence of the file
|
||||||
|
f, err := os.Stat("appicon.png")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove("appicon.png")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if f.IsDir() {
|
||||||
|
return fmt.Errorf("appicon.png is a directory")
|
||||||
|
}
|
||||||
|
if f.Size() == 0 {
|
||||||
|
return fmt.Errorf("appicon.png is empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should generate a .ico file when using the `input` flag and `windowsfilena me` flag",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "appicon.png")
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: exampleIcon,
|
||||||
|
WindowsFilename: "appicon.ico",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
test: func() error {
|
||||||
|
// the file `appicon.ico` should be created in the current directory
|
||||||
|
// check for the existence of the file
|
||||||
|
f, err := os.Stat("appicon.ico")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Remove the file
|
||||||
|
err = os.Remove("appicon.ico")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if f.IsDir() {
|
||||||
|
return fmt.Errorf("appicon.ico is a directory")
|
||||||
|
}
|
||||||
|
if f.Size() == 0 {
|
||||||
|
return fmt.Errorf("appicon.ico is empty")
|
||||||
|
}
|
||||||
|
// Remove the file
|
||||||
|
err = os.Remove("appicon.ico")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should generate a .icns file when using the `input` flag and `macfilename` flag",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "appicon.png")
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: exampleIcon,
|
||||||
|
MacFilename: "appicon.icns",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
test: func() error {
|
||||||
|
// the file `appicon.icns` should be created in the current directory
|
||||||
|
// check for the existence of the file
|
||||||
|
f, err := os.Stat("appicon.icns")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Remove the file
|
||||||
|
err = os.Remove("appicon.icns")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if f.IsDir() {
|
||||||
|
return fmt.Errorf("appicon.icns is a directory")
|
||||||
|
}
|
||||||
|
if f.Size() == 0 {
|
||||||
|
return fmt.Errorf("appicon.icns is empty")
|
||||||
|
}
|
||||||
|
// Remove the file
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should generate a small .ico file when using the `input` flag and `sizes` flag",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "appicon.png")
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: exampleIcon,
|
||||||
|
Sizes: "16",
|
||||||
|
WindowsFilename: "appicon.ico",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
test: func() error {
|
||||||
|
// the file `appicon.ico` should be created in the current directory
|
||||||
|
// check for the existence of the file
|
||||||
|
f, err := os.Stat("appicon.ico")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove("appicon.ico")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// The size of the file should be 571 bytes
|
||||||
|
if f.Size() != 571 {
|
||||||
|
return fmt.Errorf("appicon.ico is not the correct size. Got %d", f.Size())
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
return fmt.Errorf("appicon.ico is a directory")
|
||||||
|
}
|
||||||
|
if f.Size() == 0 {
|
||||||
|
return fmt.Errorf("appicon.ico is empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if no input file is provided",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
return &IconsOptions{}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if neither mac or windows filename is provided",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "appicon.png")
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: exampleIcon,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if bad sizes provided",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "appicon.png")
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: exampleIcon,
|
||||||
|
WindowsFilename: "appicon.ico",
|
||||||
|
Sizes: "bad",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should ignore 0 size",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "appicon.png")
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: exampleIcon,
|
||||||
|
WindowsFilename: "appicon.ico",
|
||||||
|
Sizes: "0,16",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
test: func() error {
|
||||||
|
// Test the file exists and has 571 bytes
|
||||||
|
f, err := os.Stat("appicon.ico")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove("appicon.ico")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if f.Size() != 571 {
|
||||||
|
return fmt.Errorf("appicon.ico is not the correct size. Got %d", f.Size())
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
return fmt.Errorf("appicon.ico is a directory")
|
||||||
|
}
|
||||||
|
if f.Size() == 0 {
|
||||||
|
return fmt.Errorf("appicon.ico is empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if the input file does not exist",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: "doesnotexist.png",
|
||||||
|
WindowsFilename: "appicon.ico",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if the input file is not a png",
|
||||||
|
setup: func() *IconsOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
return &IconsOptions{
|
||||||
|
Input: thisFile,
|
||||||
|
WindowsFilename: "appicon.ico",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
options := tt.setup()
|
||||||
|
err := GenerateIcons(options)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("GenerateIcon() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.test != nil {
|
||||||
|
if err := tt.test(); err != nil {
|
||||||
|
t.Errorf("GenerateIcon() test error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
8
v3/internal/commands/init.go
Normal file
8
v3/internal/commands/init.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
type InitOptions struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(options *InitOptions) error {
|
||||||
|
return nil
|
||||||
|
}
|
123
v3/internal/commands/syso.go
Normal file
123
v3/internal/commands/syso.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/tc-hib/winres"
|
||||||
|
"github.com/tc-hib/winres/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysoOptions struct {
|
||||||
|
Example bool `description:"Generate example manifest & info files"`
|
||||||
|
Manifest string `description:"The manifest file"`
|
||||||
|
Info string `description:"The info.json file"`
|
||||||
|
Icon string `description:"The icon file"`
|
||||||
|
Out string `description:"The output filename for the syso file"`
|
||||||
|
Arch string `description:"The target architecture"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SysoOptions) Default() *SysoOptions {
|
||||||
|
return &SysoOptions{
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateSyso(options *SysoOptions) error {
|
||||||
|
|
||||||
|
// Generate example files?
|
||||||
|
if options.Example {
|
||||||
|
return generateExampleSyso()
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Manifest == "" {
|
||||||
|
return fmt.Errorf("manifest is required")
|
||||||
|
}
|
||||||
|
if options.Icon == "" {
|
||||||
|
return fmt.Errorf("icon is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := winres.ResourceSet{}
|
||||||
|
|
||||||
|
// Process Icon
|
||||||
|
iconFile, err := os.Open(options.Icon)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer iconFile.Close()
|
||||||
|
ico, err := winres.LoadICO(iconFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't load icon '%s': %v", options.Icon, err)
|
||||||
|
}
|
||||||
|
err = rs.SetIcon(winres.RT_ICON, ico)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process Manifest
|
||||||
|
manifestData, err := os.ReadFile(options.Manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlData, err := winres.AppManifestFromXML(manifestData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rs.SetManifest(xmlData)
|
||||||
|
|
||||||
|
if options.Info != "" {
|
||||||
|
infoData, err := os.ReadFile(options.Info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(infoData) != 0 {
|
||||||
|
var v version.Info
|
||||||
|
if err := v.UnmarshalJSON(infoData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rs.SetVersionInfo(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetFile := options.Out
|
||||||
|
if targetFile == "" {
|
||||||
|
targetFile = "rsrc_windows_" + options.Arch + ".syso"
|
||||||
|
}
|
||||||
|
fout, err := os.Create(targetFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fout.Close()
|
||||||
|
|
||||||
|
archs := map[string]winres.Arch{
|
||||||
|
"amd64": winres.ArchAMD64,
|
||||||
|
"arm64": winres.ArchARM64,
|
||||||
|
"386": winres.ArchI386,
|
||||||
|
}
|
||||||
|
targetArch, supported := archs[options.Arch]
|
||||||
|
if !supported {
|
||||||
|
return fmt.Errorf("arch '%s' not supported", options.Arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rs.WriteObject(fout, targetArch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateExampleSyso() error {
|
||||||
|
// Generate example info.json
|
||||||
|
err := os.WriteFile("info.json", Info, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Generate example manifest
|
||||||
|
err = os.WriteFile("wails.exe.manifest", Manifest, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
189
v3/internal/commands/syso_test.go
Normal file
189
v3/internal/commands/syso_test.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateSyso(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func() *SysoOptions
|
||||||
|
wantErr bool
|
||||||
|
test func() error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should generate example info and manifest files when using the `example` flag",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
return &SysoOptions{
|
||||||
|
Example: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
test: func() error {
|
||||||
|
// the file `info.json` should be created in the current directory
|
||||||
|
// check for the existence of the file
|
||||||
|
f, err := os.Stat("info.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := os.Stat("wails.exe.manifest")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove("info.json")
|
||||||
|
err2 := os.Remove("wails.exe.manifest")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err2 != nil {
|
||||||
|
panic(err2)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if f.IsDir() {
|
||||||
|
return fmt.Errorf("info.json is a directory")
|
||||||
|
}
|
||||||
|
if f.Size() == 0 {
|
||||||
|
return fmt.Errorf("info.json is empty")
|
||||||
|
}
|
||||||
|
if m.IsDir() {
|
||||||
|
return fmt.Errorf("wails.exe.manifest is a directory")
|
||||||
|
}
|
||||||
|
if m.Size() == 0 {
|
||||||
|
return fmt.Errorf("wails.exe.manifest is empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if manifest filename is not provided",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if icon filename is not provided",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: "test.manifest",
|
||||||
|
Icon: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if icon filename does not exist",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: "test.manifest",
|
||||||
|
Icon: "icon.ico",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if icon is wrong format",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: "test.manifest",
|
||||||
|
Icon: thisFile,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if manifest filename does not exist",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "icon.ico")
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: "test.manifest",
|
||||||
|
Icon: exampleIcon,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if manifest is wrong format",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "icon.ico")
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: exampleIcon,
|
||||||
|
Icon: exampleIcon,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if info file does not exist",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "icon.ico")
|
||||||
|
// Get the path to the example manifest
|
||||||
|
exampleManifest := filepath.Join(localDir, "examples", "wails.exe.manifest")
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: exampleManifest,
|
||||||
|
Icon: exampleIcon,
|
||||||
|
Info: "doesnotexist.json",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should error if info file is wrong format",
|
||||||
|
setup: func() *SysoOptions {
|
||||||
|
// Get the directory of this file
|
||||||
|
_, thisFile, _, _ := runtime.Caller(1)
|
||||||
|
localDir := filepath.Dir(thisFile)
|
||||||
|
// Get the path to the example icon
|
||||||
|
exampleIcon := filepath.Join(localDir, "examples", "icon.ico")
|
||||||
|
// Get the path to the example manifest
|
||||||
|
exampleManifest := filepath.Join(localDir, "examples", "wails.exe.manifest")
|
||||||
|
return &SysoOptions{
|
||||||
|
Manifest: exampleManifest,
|
||||||
|
Icon: exampleIcon,
|
||||||
|
Info: thisFile,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
options := tt.setup()
|
||||||
|
err := GenerateSyso(options)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("GenerateSyso() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (err != nil) && tt.wantErr {
|
||||||
|
println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.test != nil {
|
||||||
|
if err := tt.test(); err != nil {
|
||||||
|
t.Errorf("GenerateSyso() test error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
66
v3/internal/commands/task.go
Normal file
66
v3/internal/commands/task.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3"
|
||||||
|
"github.com/go-task/task/v3/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RunTaskOptions struct {
|
||||||
|
Name string `name:"n" description:"The name of the task to run"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunTask(options *RunTaskOptions) error {
|
||||||
|
if options.Name == "" {
|
||||||
|
return fmt.Errorf("name of task required")
|
||||||
|
}
|
||||||
|
|
||||||
|
e := task.Executor{}
|
||||||
|
err := e.Setup()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
build := taskfile.Call{
|
||||||
|
Task: options.Name,
|
||||||
|
Vars: nil,
|
||||||
|
}
|
||||||
|
return e.Run(context.Background(), build)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListTaskOptions struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListTasks(options *ListTaskOptions) error {
|
||||||
|
e := task.Executor{}
|
||||||
|
if err := e.Setup(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tasks := e.GetTaskList()
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
return fmt.Errorf("no tasks found. Ensure there is a `Taskfile.yml` in your project. You can generate a default takfile by running `wails generate defaults`")
|
||||||
|
}
|
||||||
|
tableData := [][]string{
|
||||||
|
{"Task", "Summary"},
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
|
||||||
|
for _, thisTask := range tasks {
|
||||||
|
if thisTask.Internal {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var thisRow = make([]string, 2)
|
||||||
|
thisRow[0] = thisTask.Task
|
||||||
|
thisRow[1] = thisTask.Summary
|
||||||
|
tableData = append(tableData, thisRow)
|
||||||
|
}
|
||||||
|
err := pterm.DefaultTable.WithHasHeader(true).WithHeaderRowSeparator("-").WithData(tableData).Render()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
return nil
|
||||||
|
}
|
38
v3/internal/commands/task_test.go
Normal file
38
v3/internal/commands/task_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestBuild(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
options *RunTaskOptions
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should error if task name not provided",
|
||||||
|
args: args{
|
||||||
|
options: &RunTaskOptions{},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should work if task name provided",
|
||||||
|
args: args{
|
||||||
|
options: &RunTaskOptions{
|
||||||
|
Name: "build",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := RunTask(tt.args.options); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
24
v3/internal/parser/README.md
Normal file
24
v3/internal/parser/README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Parser
|
||||||
|
|
||||||
|
This package contains the static analyser used for parsing Wails projects so that we may:
|
||||||
|
|
||||||
|
- Generate the bindings for the frontend
|
||||||
|
- Generate Typescript definitions for the structs used by the bindings
|
||||||
|
|
||||||
|
## Implemented
|
||||||
|
|
||||||
|
- [x] Parsing of structs
|
||||||
|
- [x] Generation of models
|
||||||
|
- [x] Scalars
|
||||||
|
- [x] Arrays
|
||||||
|
- [ ] Maps
|
||||||
|
- [x] Structs
|
||||||
|
- [ ] Generation of bindings
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
There are many ways to write a Go program so there are many different program structures that we would need to support. This is a work in progress and will be improved over time. The current limitations are:
|
||||||
|
|
||||||
|
- The call to `application.New()` must be in the `main` package
|
||||||
|
- Bound structs must be declared as struct literals
|
||||||
|
|
95
v3/internal/parser/models.go
Normal file
95
v3/internal/parser/models.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/types"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateModels(context *Context) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var pkgs []Package
|
||||||
|
specs := context.GetBoundStructs()
|
||||||
|
for pkg, pkgSpecs := range specs {
|
||||||
|
pkgs = append(pkgs, Package{Name: pkg, Specs: pkgSpecs})
|
||||||
|
}
|
||||||
|
knownStructs := newAllModels(specs)
|
||||||
|
sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name })
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
|
||||||
|
for _, spec := range pkg.Specs {
|
||||||
|
if structType, ok := spec.Type.(*ast.StructType); ok {
|
||||||
|
if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range structType.Fields.List {
|
||||||
|
|
||||||
|
// Ignore field names that have a lower case first letter
|
||||||
|
if !unicode.IsUpper(rune(field.Names[0].Name[0])) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the Go type of the field
|
||||||
|
goType := types.ExprString(field.Type)
|
||||||
|
// Check if the type is an array
|
||||||
|
if arrayType, ok := field.Type.(*ast.ArrayType); ok {
|
||||||
|
// Get the element type of the array
|
||||||
|
elementType := types.ExprString(arrayType.Elt)
|
||||||
|
// Look up the corresponding TypeScript type
|
||||||
|
tsType, ok := goToTS[elementType]
|
||||||
|
if !ok {
|
||||||
|
// strip off the * prefix if it is there
|
||||||
|
if strings.HasPrefix(elementType, "*") {
|
||||||
|
elementType = elementType[1:]
|
||||||
|
}
|
||||||
|
if knownStructs.exists(elementType) {
|
||||||
|
tsType = elementType
|
||||||
|
} else {
|
||||||
|
tsType = "any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Output the field as an array of the corresponding TypeScript type
|
||||||
|
if _, err := fmt.Fprintf(&buf, " %s: %s[];\n", field.Names[0].Name, tsType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// strip off the * prefix if it is there
|
||||||
|
if strings.HasPrefix(goType, "*") {
|
||||||
|
goType = goType[1:]
|
||||||
|
}
|
||||||
|
// Look up the corresponding TypeScript type
|
||||||
|
tsType, ok := goToTS[goType]
|
||||||
|
if !ok {
|
||||||
|
if knownStructs.exists(goType) {
|
||||||
|
tsType = goType
|
||||||
|
} else {
|
||||||
|
tsType = "any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Output the field as the corresponding TypeScript type
|
||||||
|
if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(&buf, "}\n\n"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
531
v3/internal/parser/parser.go
Normal file
531
v3/internal/parser/parser.go
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Debug = false
|
||||||
|
|
||||||
|
func debug(msg string, args ...interface{}) {
|
||||||
|
if Debug {
|
||||||
|
println(fmt.Sprintf(msg, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type parsedPackage struct {
|
||||||
|
name string
|
||||||
|
pkg *ast.Package
|
||||||
|
boundStructs map[string]*ast.TypeSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
packages map[string]*parsedPackage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetBoundStructs() map[string][]*ast.TypeSpec {
|
||||||
|
structs := make(map[string][]*ast.TypeSpec)
|
||||||
|
for _, pkg := range c.packages {
|
||||||
|
for _, structType := range pkg.boundStructs {
|
||||||
|
structs[pkg.name] = append(structs[pkg.name], structType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return structs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseDirectory(dir string) (*Context, error) {
|
||||||
|
// Parse the directory
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
if dir == "." || dir == "" {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dir = cwd
|
||||||
|
}
|
||||||
|
println("Parsing directory " + dir)
|
||||||
|
pkgs, err := parser.ParseDir(fset, dir, nil, parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
context := &Context{
|
||||||
|
packages: make(map[string]*parsedPackage),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through the packages
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
context.packages[pkg.Name] = &parsedPackage{
|
||||||
|
name: pkg.Name,
|
||||||
|
pkg: pkg,
|
||||||
|
boundStructs: make(map[string]*ast.TypeSpec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findApplicationNewCalls(context)
|
||||||
|
|
||||||
|
return context, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findApplicationNewCalls(context *Context) {
|
||||||
|
// Iterate through the packages
|
||||||
|
currentPackages := lo.Keys(context.packages)
|
||||||
|
|
||||||
|
for _, packageName := range currentPackages {
|
||||||
|
thisPackage := context.packages[packageName]
|
||||||
|
debug("Parsing package: %s", packageName)
|
||||||
|
// Iterate through the package's files
|
||||||
|
for _, file := range thisPackage.pkg.Files {
|
||||||
|
// Use an ast.Inspector to find the calls to application.New
|
||||||
|
ast.Inspect(file, func(n ast.Node) bool {
|
||||||
|
// Check if the node is a call expression
|
||||||
|
callExpr, ok := n.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the function being called is "application.New"
|
||||||
|
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if selExpr.Sel.Name != "New" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if id, ok := selExpr.X.(*ast.Ident); !ok || id.Name != "application" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check there is only 1 argument
|
||||||
|
if len(callExpr.Args) != 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check argument 1 is a struct literal
|
||||||
|
structLit, ok := callExpr.Args[0].(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check struct literal is of type "options.Application"
|
||||||
|
selectorExpr, ok := structLit.Type.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if selectorExpr.Sel.Name != "Application" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "options" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, elt := range structLit.Elts {
|
||||||
|
// Find the "Bind" field
|
||||||
|
kvExpr, ok := elt.(*ast.KeyValueExpr)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if id, ok := kvExpr.Key.(*ast.Ident); !ok || id.Name != "Bind" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check the value is a slice of interfaces
|
||||||
|
sliceExpr, ok := kvExpr.Value.(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var arrayType *ast.ArrayType
|
||||||
|
if arrayType, ok = sliceExpr.Type.(*ast.ArrayType); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check array type is of type "interface{}"
|
||||||
|
if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Iterate through the slice elements
|
||||||
|
for _, elt := range sliceExpr.Elts {
|
||||||
|
// Check the element is a unary expression
|
||||||
|
unaryExpr, ok := elt.(*ast.UnaryExpr)
|
||||||
|
if ok {
|
||||||
|
// Check the unary expression is a composite lit
|
||||||
|
boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the composite lit is a struct
|
||||||
|
if _, ok := boundStructLit.Type.(*ast.StructType); ok {
|
||||||
|
// Parse struct
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the lit is an ident
|
||||||
|
ident, ok := boundStructLit.Type.(*ast.Ident)
|
||||||
|
if ok {
|
||||||
|
if ident.Obj == nil {
|
||||||
|
structTypeSpec := findStructInPackage(thisPackage.pkg, ident.Name)
|
||||||
|
thisPackage.boundStructs[ident.Name] = structTypeSpec
|
||||||
|
findNestedStructs(structTypeSpec, file, packageName, context)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the ident is a struct type
|
||||||
|
if t, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
||||||
|
thisPackage.boundStructs[ident.Name] = t
|
||||||
|
findNestedStructs(t, file, packageName, context)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check the typespec decl is a struct
|
||||||
|
if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// Check if the lit is a selector
|
||||||
|
selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
|
||||||
|
if ok {
|
||||||
|
getStructsFromSelector(selector, file, context)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStructsFromSelector(selector *ast.SelectorExpr, file *ast.File, context *Context) {
|
||||||
|
debug("getStructsFromSelector called with selector '%s' on file '%s.go'", selector.Sel.Name, file.Name.Name)
|
||||||
|
|
||||||
|
// extract package name from selector
|
||||||
|
packageName := selector.X.(*ast.Ident).Name
|
||||||
|
|
||||||
|
if context.packages[packageName] == nil {
|
||||||
|
context.packages[packageName] = &parsedPackage{
|
||||||
|
name: packageName,
|
||||||
|
boundStructs: make(map[string]*ast.TypeSpec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract struct name from selector
|
||||||
|
structName := selector.Sel.Name
|
||||||
|
|
||||||
|
// Find the package name from the imports
|
||||||
|
for _, imp := range file.Imports {
|
||||||
|
var match bool
|
||||||
|
if imp.Name == nil || imp.Name.Name == packageName {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
if match == false {
|
||||||
|
pathSplit := strings.Split(imp.Path.Value, "/")
|
||||||
|
endPath := strings.Trim(pathSplit[len(pathSplit)-1], `"`)
|
||||||
|
match = endPath == packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
// We have the import
|
||||||
|
cfg := &packages.Config{
|
||||||
|
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedModule,
|
||||||
|
}
|
||||||
|
pkgs, err := packages.Load(cfg, strings.Trim(imp.Path.Value, `"`))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
foundPackage := pkgs[0]
|
||||||
|
|
||||||
|
// Iterate the files in the package and find struct types
|
||||||
|
for _, parsedFile := range foundPackage.Syntax {
|
||||||
|
ast.Inspect(parsedFile, func(n ast.Node) bool {
|
||||||
|
if n == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch n.(type) {
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
typeSpec := n.(*ast.TypeSpec)
|
||||||
|
if typeSpec.Name.Name == structName {
|
||||||
|
if _, ok := context.packages[packageName].boundStructs[structName]; !ok {
|
||||||
|
debug("Adding struct '%s' in package '%s'", structName, packageName)
|
||||||
|
context.packages[packageName].boundStructs[typeSpec.Name.Name] = typeSpec
|
||||||
|
findNestedStructs(typeSpec, parsedFile, packageName, context)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNestedStructs(t *ast.TypeSpec, parsedFile *ast.File, pkgName string, context *Context) {
|
||||||
|
debug("findNestedStructs called with type '%s' on file '%s.go'", t.Name.Name, parsedFile.Name.Name)
|
||||||
|
structType, ok := t.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, field := range structType.Fields.List {
|
||||||
|
for _, ident := range field.Names {
|
||||||
|
switch t := ident.Obj.Decl.(*ast.Field).Type.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
if t.Obj == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.Obj.Kind == ast.Typ {
|
||||||
|
if _, ok := t.Obj.Decl.(*ast.TypeSpec); ok {
|
||||||
|
if _, ok := context.packages[pkgName].boundStructs[t.Name]; !ok {
|
||||||
|
debug("Adding nested struct '%s' to package '%s'", t.Name, pkgName)
|
||||||
|
context.packages[pkgName].boundStructs[t.Name] = t.Obj.Decl.(*ast.TypeSpec)
|
||||||
|
findNestedStructs(t.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
if ident, ok := t.X.(*ast.Ident); ok {
|
||||||
|
if ident.IsExported() {
|
||||||
|
getStructsFromSelector(t, parsedFile, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.StarExpr:
|
||||||
|
if sel, ok := t.X.(*ast.SelectorExpr); ok {
|
||||||
|
if _, ok := sel.X.(*ast.Ident); ok {
|
||||||
|
if ident.IsExported() {
|
||||||
|
getStructsFromSelector(sel, parsedFile, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findStructsInMethods(t.Name.Name, parsedFile, pkgName, context)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func findStructsInMethods(name string, parsedFile *ast.File, pkgName string, context *Context) {
|
||||||
|
debug("findStructsInMethods called with type '%s' on file '%s.go'", name, parsedFile.Name.Name)
|
||||||
|
// Find the struct declaration for the given name
|
||||||
|
var structDecl *ast.TypeSpec
|
||||||
|
for _, decl := range parsedFile.Decls {
|
||||||
|
if fn, ok := decl.(*ast.FuncDecl); ok {
|
||||||
|
// check the receiver name is the same as the name given
|
||||||
|
if fn.Recv == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the receiver is a pointer
|
||||||
|
if starExpr, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
|
||||||
|
if ident, ok := starExpr.X.(*ast.Ident); ok {
|
||||||
|
if ident.Name != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ident, ok := fn.Recv.List[0].Type.(*ast.Ident); ok {
|
||||||
|
if ident.Name != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findStructsInMethodParams(fn, parsedFile, pkgName, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if structDecl == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Iterate the methods in the struct
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func findStructsInMethodParams(f *ast.FuncDecl, parsedFile *ast.File, pkgName string, context *Context) {
|
||||||
|
debug("findStructsInMethodParams called with type '%s' on file '%s.go'", f.Name.Name, parsedFile.Name.Name)
|
||||||
|
if f.Type.Params == nil {
|
||||||
|
for _, field := range f.Type.Params.List {
|
||||||
|
parseField(field, parsedFile, pkgName, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f.Type.Results != nil {
|
||||||
|
for _, field := range f.Type.Results.List {
|
||||||
|
parseField(field, parsedFile, pkgName, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseField(field *ast.Field, parsedFile *ast.File, pkgName string, context *Context) {
|
||||||
|
if se, ok := field.Type.(*ast.StarExpr); ok {
|
||||||
|
// Check if the star expr is a struct
|
||||||
|
if selExp, ok := se.X.(*ast.SelectorExpr); ok {
|
||||||
|
getStructsFromSelector(selExp, parsedFile, context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ident, ok := se.X.(*ast.Ident); ok {
|
||||||
|
if ident.Obj == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ident.Obj.Kind == ast.Typ {
|
||||||
|
if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
||||||
|
if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
|
||||||
|
debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
|
||||||
|
context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
|
||||||
|
findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
|
||||||
|
} else {
|
||||||
|
debug("Struct %s already bound", ident.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if selExp, ok := field.Type.(*ast.SelectorExpr); ok {
|
||||||
|
getStructsFromSelector(selExp, parsedFile, context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ident, ok := field.Type.(*ast.Ident); ok {
|
||||||
|
if ident.Obj == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ident.Obj.Kind == ast.Typ {
|
||||||
|
if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
||||||
|
if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
|
||||||
|
debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
|
||||||
|
context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
|
||||||
|
findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
|
||||||
|
} else {
|
||||||
|
debug("Struct %s already bound", ident.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findStructInPackage(pkg *ast.Package, name string) *ast.TypeSpec {
|
||||||
|
for _, file := range pkg.Files {
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.TYPE {
|
||||||
|
for _, spec := range gen.Specs {
|
||||||
|
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
||||||
|
if typeSpec.Name.Name == name {
|
||||||
|
if _, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||||
|
return typeSpec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Package struct {
|
||||||
|
Name string
|
||||||
|
Specs []*ast.TypeSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
var goToTS = map[string]string{
|
||||||
|
"int": "number",
|
||||||
|
"int8": "number",
|
||||||
|
"int16": "number",
|
||||||
|
"int32": "number",
|
||||||
|
"int64": "number",
|
||||||
|
"uint": "number",
|
||||||
|
"uint8": "number",
|
||||||
|
"uint16": "number",
|
||||||
|
"uint32": "number",
|
||||||
|
"uint64": "number",
|
||||||
|
"float32": "number",
|
||||||
|
"float64": "number",
|
||||||
|
"string": "string",
|
||||||
|
"bool": "boolean",
|
||||||
|
}
|
||||||
|
|
||||||
|
//func GenerateModels(specs map[string][]*ast.TypeSpec) ([]byte, error) {
|
||||||
|
// var buf bytes.Buffer
|
||||||
|
// var packages []Package
|
||||||
|
// for pkg, pkgSpecs := range specs {
|
||||||
|
// packages = append(packages, Package{Name: pkg, Specs: pkgSpecs})
|
||||||
|
// }
|
||||||
|
// sort.Slice(packages, func(i, j int) bool { return packages[i].Name < packages[j].Name })
|
||||||
|
// for _, pkg := range packages {
|
||||||
|
// if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
|
||||||
|
// for _, spec := range pkg.Specs {
|
||||||
|
// if structType, ok := spec.Type.(*ast.StructType); ok {
|
||||||
|
// if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for _, field := range structType.Fields.List {
|
||||||
|
//
|
||||||
|
// // Get the Go type of the field
|
||||||
|
// goType := types.ExprString(field.Type)
|
||||||
|
// // Look up the corresponding TypeScript type
|
||||||
|
// tsType, ok := goToTS[goType]
|
||||||
|
// if !ok {
|
||||||
|
// tsType = goType
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if _, err := fmt.Fprintf(&buf, "}\n"); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return buf.Bytes(), nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
type allModels struct {
|
||||||
|
known map[string]map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAllModels(models map[string][]*ast.TypeSpec) *allModels {
|
||||||
|
result := &allModels{known: make(map[string]map[string]struct{})}
|
||||||
|
// iterate over all models
|
||||||
|
for pkg, pkgSpecs := range models {
|
||||||
|
for _, spec := range pkgSpecs {
|
||||||
|
result.known[pkg] = make(map[string]struct{})
|
||||||
|
result.known[pkg][spec.Name.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *allModels) exists(name string) bool {
|
||||||
|
// Split the name into package and type
|
||||||
|
parts := strings.Split(name, ".")
|
||||||
|
typ := parts[0]
|
||||||
|
pkg := "main"
|
||||||
|
if len(parts) == 2 {
|
||||||
|
pkg = parts[0]
|
||||||
|
typ = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
knownPkg, ok := k.known[pkg]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok = knownPkg[typ]
|
||||||
|
return ok
|
||||||
|
}
|
144
v3/internal/parser/parser_test.go
Normal file
144
v3/internal/parser/parser_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseDirectory(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dir string
|
||||||
|
want []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should find single bound service",
|
||||||
|
dir: "testdata/struct_literal_single",
|
||||||
|
want: []string{"main.GreetService"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should find multiple bound services",
|
||||||
|
dir: "testdata/struct_literal_multiple",
|
||||||
|
want: []string{"main.GreetService", "main.OtherService"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should find multiple bound services over multiple files",
|
||||||
|
dir: "testdata/struct_literal_multiple_files",
|
||||||
|
want: []string{"main.GreetService", "main.OtherService"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should find bound services from other packages",
|
||||||
|
dir: "../../examples/binding",
|
||||||
|
want: []string{"main.localStruct", "services.GreetService", "models.Person"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
Debug = true
|
||||||
|
got, err := ParseDirectory(tt.dir)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, pkg := range got.packages {
|
||||||
|
for structName := range pkg.boundStructs {
|
||||||
|
require.True(t, lo.Contains(tt.want, name+"."+structName))
|
||||||
|
tt.want = lo.Without(tt.want, name+"."+structName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Empty(t, tt.want)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateTypeScript(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dir string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should find single bound service",
|
||||||
|
dir: "testdata/struct_literal_single",
|
||||||
|
want: `namespace main {
|
||||||
|
class GreetService {
|
||||||
|
SomeVariable: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should find multiple bound services",
|
||||||
|
dir: "testdata/struct_literal_multiple",
|
||||||
|
want: `namespace main {
|
||||||
|
class GreetService {
|
||||||
|
SomeVariable: number;
|
||||||
|
}
|
||||||
|
class OtherService {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should find multiple bound services over multiple files",
|
||||||
|
dir: "testdata/struct_literal_multiple_files",
|
||||||
|
want: `namespace main {
|
||||||
|
class GreetService {
|
||||||
|
SomeVariable: number;
|
||||||
|
}
|
||||||
|
class OtherService {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should find bound services from other packages",
|
||||||
|
dir: "../../examples/binding",
|
||||||
|
want: `namespace main {
|
||||||
|
class localStruct {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace models {
|
||||||
|
class Person {
|
||||||
|
Name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace services {
|
||||||
|
class GreetService {
|
||||||
|
SomeVariable: number;
|
||||||
|
Parent: models.Person;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
Debug = true
|
||||||
|
context, err := ParseDirectory(tt.dir)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, err := GenerateModels(context)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.want, string(ts))
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
40
v3/internal/parser/testdata/struct_literal_multiple/main.go
vendored
Normal file
40
v3/internal/parser/testdata/struct_literal_multiple/main.go
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GreetService struct {
|
||||||
|
SomeVariable int
|
||||||
|
lowerCase string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GreetService) Greet(name string) string {
|
||||||
|
return "Hello " + name
|
||||||
|
}
|
||||||
|
|
||||||
|
type OtherService struct {
|
||||||
|
t int
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Bind: []interface{}{
|
||||||
|
&GreetService{},
|
||||||
|
&OtherService{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
v3/internal/parser/testdata/struct_literal_multiple_files/greet.go
vendored
Normal file
14
v3/internal/parser/testdata/struct_literal_multiple_files/greet.go
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GreetService struct {
|
||||||
|
SomeVariable int
|
||||||
|
lowerCase string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GreetService) Greet(name string) string {
|
||||||
|
return "Hello " + name
|
||||||
|
}
|
27
v3/internal/parser/testdata/struct_literal_multiple_files/main.go
vendored
Normal file
27
v3/internal/parser/testdata/struct_literal_multiple_files/main.go
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Bind: []interface{}{
|
||||||
|
&GreetService{},
|
||||||
|
&OtherService{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
v3/internal/parser/testdata/struct_literal_multiple_files/other.go
vendored
Normal file
5
v3/internal/parser/testdata/struct_literal_multiple_files/other.go
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type OtherService struct {
|
||||||
|
t int
|
||||||
|
}
|
35
v3/internal/parser/testdata/struct_literal_single/main.go
vendored
Normal file
35
v3/internal/parser/testdata/struct_literal_single/main.go
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GreetService struct {
|
||||||
|
SomeVariable int
|
||||||
|
lowerCase string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GreetService) Greet(name string) string {
|
||||||
|
return "Hello " + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(options.Application{
|
||||||
|
Bind: []interface{}{
|
||||||
|
&GreetService{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.NewWebviewWindow()
|
||||||
|
|
||||||
|
err := app.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
v3/internal/runtime/assets.go
Normal file
26
v3/internal/runtime/assets.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//go:build production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
var RuntimeAssetsBundle = &RuntimeAssets{
|
||||||
|
desktopIPC: DesktopIPC,
|
||||||
|
runtimeDesktopJS: DesktopRuntime,
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeAssets struct {
|
||||||
|
desktopIPC []byte
|
||||||
|
websocketIPC []byte
|
||||||
|
runtimeDesktopJS []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuntimeAssets) DesktopIPC() []byte {
|
||||||
|
return r.desktopIPC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuntimeAssets) WebsocketIPC() []byte {
|
||||||
|
return r.websocketIPC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuntimeAssets) RuntimeDesktopJS() []byte {
|
||||||
|
return r.runtimeDesktopJS
|
||||||
|
}
|
27
v3/internal/runtime/assets_dev.go
Normal file
27
v3/internal/runtime/assets_dev.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//go:build !production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
var RuntimeAssetsBundle = &RuntimeAssets{
|
||||||
|
desktopIPC: DesktopIPC,
|
||||||
|
websocketIPC: WebsocketIPC,
|
||||||
|
runtimeDesktopJS: DesktopRuntime,
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeAssets struct {
|
||||||
|
desktopIPC []byte
|
||||||
|
websocketIPC []byte
|
||||||
|
runtimeDesktopJS []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuntimeAssets) DesktopIPC() []byte {
|
||||||
|
return r.desktopIPC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuntimeAssets) WebsocketIPC() []byte {
|
||||||
|
return r.websocketIPC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuntimeAssets) RuntimeDesktopJS() []byte {
|
||||||
|
return r.runtimeDesktopJS
|
||||||
|
}
|
67
v3/internal/runtime/desktop/bindings.js
Normal file
67
v3/internal/runtime/desktop/bindings.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
import {Call} from './calls';
|
||||||
|
|
||||||
|
// This is where we bind go method wrappers
|
||||||
|
window.go = {};
|
||||||
|
|
||||||
|
export function SetBindings(bindingsMap) {
|
||||||
|
try {
|
||||||
|
bindingsMap = JSON.parse(bindingsMap);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise the bindings map
|
||||||
|
window.go = window.go || {};
|
||||||
|
|
||||||
|
// Iterate package names
|
||||||
|
Object.keys(bindingsMap).forEach((packageName) => {
|
||||||
|
|
||||||
|
// Create inner map if it doesn't exist
|
||||||
|
window.go[packageName] = window.go[packageName] || {};
|
||||||
|
|
||||||
|
// Iterate struct names
|
||||||
|
Object.keys(bindingsMap[packageName]).forEach((structName) => {
|
||||||
|
|
||||||
|
// Create inner map if it doesn't exist
|
||||||
|
window.go[packageName][structName] = window.go[packageName][structName] || {};
|
||||||
|
|
||||||
|
Object.keys(bindingsMap[packageName][structName]).forEach((methodName) => {
|
||||||
|
|
||||||
|
window.go[packageName][structName][methodName] = function () {
|
||||||
|
|
||||||
|
// No timeout by default
|
||||||
|
let timeout = 0;
|
||||||
|
|
||||||
|
// Actual function
|
||||||
|
function dynamic() {
|
||||||
|
const args = [].slice.call(arguments);
|
||||||
|
return Call([packageName, structName, methodName].join('.'), args, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow setting timeout to function
|
||||||
|
dynamic.setTimeout = function (newTimeout) {
|
||||||
|
timeout = newTimeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allow getting timeout to function
|
||||||
|
dynamic.getTimeout = function () {
|
||||||
|
return timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
return dynamic;
|
||||||
|
}();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
8
v3/internal/runtime/desktop/browser.js
Normal file
8
v3/internal/runtime/desktop/browser.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @description: Use the system default browser to open the url
|
||||||
|
* @param {string} url
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
export function BrowserOpenURL(url) {
|
||||||
|
window.WailsInvoke('BO:' + url);
|
||||||
|
}
|
189
v3/internal/runtime/desktop/calls.js
Normal file
189
v3/internal/runtime/desktop/calls.js
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
export const callbacks = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a number from the native browser random function
|
||||||
|
*
|
||||||
|
* @returns number
|
||||||
|
*/
|
||||||
|
function cryptoRandom() {
|
||||||
|
var array = new Uint32Array(1);
|
||||||
|
return window.crypto.getRandomValues(array)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a number using da old-skool Math.Random
|
||||||
|
* I likes to call it LOLRandom
|
||||||
|
*
|
||||||
|
* @returns number
|
||||||
|
*/
|
||||||
|
function basicRandom() {
|
||||||
|
return Math.random() * 9007199254740991;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick a random number function based on browser capability
|
||||||
|
var randomFunc;
|
||||||
|
if (window.crypto) {
|
||||||
|
randomFunc = cryptoRandom;
|
||||||
|
} else {
|
||||||
|
randomFunc = basicRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call sends a message to the backend to call the binding with the
|
||||||
|
* given data. A promise is returned and will be completed when the
|
||||||
|
* backend responds. This will be resolved when the call was successful
|
||||||
|
* or rejected if an error is passed back.
|
||||||
|
* There is a timeout mechanism. If the call doesn't respond in the given
|
||||||
|
* time (in milliseconds) then the promise is rejected.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} name
|
||||||
|
* @param {any=} args
|
||||||
|
* @param {number=} timeout
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function Call(name, args, timeout) {
|
||||||
|
|
||||||
|
// Timeout infinite by default
|
||||||
|
if (timeout == null) {
|
||||||
|
timeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let windowID = window.wails.window.ID();
|
||||||
|
|
||||||
|
// Create a promise
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
|
||||||
|
// Create a unique callbackID
|
||||||
|
var callbackID;
|
||||||
|
do {
|
||||||
|
callbackID = name + '-' + randomFunc();
|
||||||
|
} while (callbacks[callbackID]);
|
||||||
|
|
||||||
|
var timeoutHandle;
|
||||||
|
// Set timeout
|
||||||
|
if (timeout > 0) {
|
||||||
|
timeoutHandle = setTimeout(function () {
|
||||||
|
reject(Error('Call to ' + name + ' timed out. Request ID: ' + callbackID));
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store callback
|
||||||
|
callbacks[callbackID] = {
|
||||||
|
timeoutHandle: timeoutHandle,
|
||||||
|
reject: reject,
|
||||||
|
resolve: resolve
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
callbackID,
|
||||||
|
windowID,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make the call
|
||||||
|
window.WailsInvoke('C' + JSON.stringify(payload));
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.ObfuscatedCall = (id, args, timeout) => {
|
||||||
|
|
||||||
|
// Timeout infinite by default
|
||||||
|
if (timeout == null) {
|
||||||
|
timeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a promise
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
|
||||||
|
// Create a unique callbackID
|
||||||
|
var callbackID;
|
||||||
|
do {
|
||||||
|
callbackID = id + '-' + randomFunc();
|
||||||
|
} while (callbacks[callbackID]);
|
||||||
|
|
||||||
|
var timeoutHandle;
|
||||||
|
// Set timeout
|
||||||
|
if (timeout > 0) {
|
||||||
|
timeoutHandle = setTimeout(function () {
|
||||||
|
reject(Error('Call to method ' + id + ' timed out. Request ID: ' + callbackID));
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store callback
|
||||||
|
callbacks[callbackID] = {
|
||||||
|
timeoutHandle: timeoutHandle,
|
||||||
|
reject: reject,
|
||||||
|
resolve: resolve
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
id,
|
||||||
|
args,
|
||||||
|
callbackID,
|
||||||
|
windowID: window.wails.window.ID(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make the call
|
||||||
|
window.WailsInvoke('c' + JSON.stringify(payload));
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the backend to return data to a previously called
|
||||||
|
* binding invocation
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} incomingMessage
|
||||||
|
*/
|
||||||
|
export function Callback(incomingMessage) {
|
||||||
|
// Parse the message
|
||||||
|
let message;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(incomingMessage);
|
||||||
|
} catch (e) {
|
||||||
|
const error = `Invalid JSON passed to callback: ${e.message}. Message: ${incomingMessage}`;
|
||||||
|
runtime.LogDebug(error);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
let callbackID = message.callbackid;
|
||||||
|
let callbackData = callbacks[callbackID];
|
||||||
|
if (!callbackData) {
|
||||||
|
const error = `Callback '${callbackID}' not registered!!!`;
|
||||||
|
console.error(error); // eslint-disable-line
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
clearTimeout(callbackData.timeoutHandle);
|
||||||
|
|
||||||
|
delete callbacks[callbackID];
|
||||||
|
|
||||||
|
if (message.error) {
|
||||||
|
callbackData.reject(message.error);
|
||||||
|
} else {
|
||||||
|
callbackData.resolve(message.result);
|
||||||
|
}
|
||||||
|
}
|
212
v3/internal/runtime/desktop/events.js
Normal file
212
v3/internal/runtime/desktop/events.js
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
// Defines a single listener with a maximum number of times to callback
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Listener class defines a listener! :-)
|
||||||
|
*
|
||||||
|
* @class Listener
|
||||||
|
*/
|
||||||
|
class Listener {
|
||||||
|
/**
|
||||||
|
* Creates an instance of Listener.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function} callback
|
||||||
|
* @param {number} maxCallbacks
|
||||||
|
* @memberof Listener
|
||||||
|
*/
|
||||||
|
constructor(eventName, callback, maxCallbacks) {
|
||||||
|
this.eventName = eventName;
|
||||||
|
// Default of -1 means infinite
|
||||||
|
this.maxCallbacks = maxCallbacks || -1;
|
||||||
|
// Callback invokes the callback with the given data
|
||||||
|
// Returns true if this listener should be destroyed
|
||||||
|
this.Callback = (data) => {
|
||||||
|
callback.apply(null, data);
|
||||||
|
// If maxCallbacks is infinite, return false (do not destroy)
|
||||||
|
if (this.maxCallbacks === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Decrement maxCallbacks. Return true if now 0, otherwise false
|
||||||
|
this.maxCallbacks -= 1;
|
||||||
|
return this.maxCallbacks === 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const eventListeners = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function} callback
|
||||||
|
* @param {number} maxCallbacks
|
||||||
|
* @returns {function} A function to cancel the listener
|
||||||
|
*/
|
||||||
|
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||||
|
eventListeners[eventName] = eventListeners[eventName] || [];
|
||||||
|
const thisListener = new Listener(eventName, callback, maxCallbacks);
|
||||||
|
eventListeners[eventName].push(thisListener);
|
||||||
|
return () => listenerOff(thisListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an event listener that will be invoked every time the event is emitted
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function} callback
|
||||||
|
* @returns {function} A function to cancel the listener
|
||||||
|
*/
|
||||||
|
export function EventsOn(eventName, callback) {
|
||||||
|
return EventsOnMultiple(eventName, callback, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an event listener that will be invoked once then destroyed
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function} callback
|
||||||
|
* @returns {function} A function to cancel the listener
|
||||||
|
*/
|
||||||
|
export function EventsOnce(eventName, callback) {
|
||||||
|
return EventsOnMultiple(eventName, callback, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyListeners(eventData) {
|
||||||
|
|
||||||
|
// Get the event name
|
||||||
|
let eventName = eventData.name;
|
||||||
|
|
||||||
|
// Check if we have any listeners for this event
|
||||||
|
if (eventListeners[eventName]) {
|
||||||
|
|
||||||
|
// Keep a list of listener indexes to destroy
|
||||||
|
const newEventListenerList = eventListeners[eventName].slice();
|
||||||
|
|
||||||
|
// Iterate listeners
|
||||||
|
for (let count = 0; count < eventListeners[eventName].length; count += 1) {
|
||||||
|
|
||||||
|
// Get next listener
|
||||||
|
const listener = eventListeners[eventName][count];
|
||||||
|
|
||||||
|
let data = eventData.data;
|
||||||
|
|
||||||
|
// Do the callback
|
||||||
|
const destroy = listener.Callback(data);
|
||||||
|
if (destroy) {
|
||||||
|
// if the listener indicated to destroy itself, add it to the destroy list
|
||||||
|
newEventListenerList.splice(count, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update callbacks with new list of listeners
|
||||||
|
if (newEventListenerList.length === 0) {
|
||||||
|
removeListener(eventName);
|
||||||
|
} else {
|
||||||
|
eventListeners[eventName] = newEventListenerList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify informs frontend listeners that an event was emitted with the given data
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} notifyMessage - encoded notification message
|
||||||
|
|
||||||
|
*/
|
||||||
|
export function EventsNotify(notifyMessage) {
|
||||||
|
// Parse the message
|
||||||
|
let message;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(notifyMessage);
|
||||||
|
} catch (e) {
|
||||||
|
const error = 'Invalid JSON passed to Notify: ' + notifyMessage;
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
notifyListeners(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event with the given name and data
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} eventName
|
||||||
|
*/
|
||||||
|
export function EventsEmit(eventName) {
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: eventName,
|
||||||
|
data: [].slice.apply(arguments).slice(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Notify JS listeners
|
||||||
|
notifyListeners(payload);
|
||||||
|
|
||||||
|
// Notify Go listeners
|
||||||
|
window.WailsInvoke('EE' + JSON.stringify(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListener(eventName) {
|
||||||
|
// Remove local listeners
|
||||||
|
delete eventListeners[eventName];
|
||||||
|
|
||||||
|
// Notify Go listeners
|
||||||
|
window.WailsInvoke('EX' + eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Off unregisters a listener previously registered with On,
|
||||||
|
* optionally multiple listeneres can be unregistered via `additionalEventNames`
|
||||||
|
*
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {...string} additionalEventNames
|
||||||
|
*/
|
||||||
|
export function EventsOff(eventName, ...additionalEventNames) {
|
||||||
|
removeListener(eventName)
|
||||||
|
|
||||||
|
if (additionalEventNames.length > 0) {
|
||||||
|
additionalEventNames.forEach(eventName => {
|
||||||
|
removeListener(eventName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Off unregisters all event listeners previously registered with On
|
||||||
|
*/
|
||||||
|
export function EventsOffAll() {
|
||||||
|
const eventNames = Object.keys(eventListeners);
|
||||||
|
for (let i = 0; i !== eventNames.length; i++) {
|
||||||
|
removeListener(eventNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* listenerOff unregisters a listener previously registered with EventsOn
|
||||||
|
*
|
||||||
|
* @param {Listener} listener
|
||||||
|
*/
|
||||||
|
function listenerOff(listener) {
|
||||||
|
const eventName = listener.eventName;
|
||||||
|
// Remove local listener
|
||||||
|
eventListeners[eventName] = eventListeners[eventName].filter(l => l !== listener);
|
||||||
|
|
||||||
|
// Clean up if there are no event listeners left
|
||||||
|
if (eventListeners[eventName].length === 0) {
|
||||||
|
removeListener(eventName);
|
||||||
|
}
|
||||||
|
}
|
132
v3/internal/runtime/desktop/events.test.js
Normal file
132
v3/internal/runtime/desktop/events.test.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { EventsOnMultiple, EventsNotify, eventListeners, EventsOn, EventsEmit, EventsOffAll, EventsOnce, EventsOff } from './events'
|
||||||
|
import { expect, describe, it, beforeAll, vi, afterEach, beforeEach } from 'vitest'
|
||||||
|
// Edit an assertion and save to see HMR in action
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
window.WailsInvoke = vi.fn(() => {})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
EventsOffAll();
|
||||||
|
vi.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('EventsOnMultiple', () => {
|
||||||
|
it('should stop after a specified number of times', () => {
|
||||||
|
const cb = vi.fn()
|
||||||
|
EventsOnMultiple('a', cb, 5)
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
expect(cb).toBeCalledTimes(5);
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(1);
|
||||||
|
expect(window.WailsInvoke).toHaveBeenLastCalledWith('EXa');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a cancel fn', () => {
|
||||||
|
const cb = vi.fn()
|
||||||
|
const cancel = EventsOnMultiple('a', cb, 5)
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
cancel()
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: {}}))
|
||||||
|
expect(cb).toBeCalledTimes(2)
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(1);
|
||||||
|
expect(window.WailsInvoke).toHaveBeenLastCalledWith('EXa');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('EventsOn', () => {
|
||||||
|
it('should create a listener with a count of -1', () => {
|
||||||
|
EventsOn('a', () => {})
|
||||||
|
expect(eventListeners['a'][0].maxCallbacks).toBe(-1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a cancel fn', () => {
|
||||||
|
const cancel = EventsOn('a', () => {})
|
||||||
|
cancel();
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(1);
|
||||||
|
expect(window.WailsInvoke).toHaveBeenLastCalledWith('EXa');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('EventsOnce', () => {
|
||||||
|
it('should create a listener with a count of 1', () => {
|
||||||
|
EventsOnce('a', () => {})
|
||||||
|
expect(eventListeners['a'][0].maxCallbacks).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a cancel fn', () => {
|
||||||
|
const cancel = EventsOn('a', () => {})
|
||||||
|
cancel();
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(1);
|
||||||
|
expect(window.WailsInvoke).toHaveBeenLastCalledWith('EXa');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('EventsNotify', () => {
|
||||||
|
it('should inform a listener', () => {
|
||||||
|
const cb = vi.fn()
|
||||||
|
EventsOn('a', cb)
|
||||||
|
EventsNotify(JSON.stringify({name: 'a', data: ["one", "two", "three"]}))
|
||||||
|
expect(cb).toBeCalledTimes(1);
|
||||||
|
expect(cb).toHaveBeenLastCalledWith("one", "two", "three");
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(0);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('EventsEmit', () => {
|
||||||
|
it('should emit an event', () => {
|
||||||
|
EventsEmit('a', 'one', 'two', 'three')
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(1);
|
||||||
|
const calledWith = window.WailsInvoke.calls[0][0];
|
||||||
|
expect(calledWith.slice(0, 2)).toBe('EE')
|
||||||
|
expect(JSON.parse(calledWith.slice(2))).toStrictEqual({data: ["one", "two", "three"], name: "a"})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('EventsOff', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
EventsOn('a', () => {})
|
||||||
|
EventsOn('a', () => {})
|
||||||
|
EventsOn('a', () => {})
|
||||||
|
EventsOn('b', () => {})
|
||||||
|
EventsOn('c', () => {})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should cancel all event listeners for a single type', () => {
|
||||||
|
EventsOff('a')
|
||||||
|
expect(eventListeners['a']).toBeUndefined()
|
||||||
|
expect(eventListeners['b']).not.toBeUndefined()
|
||||||
|
expect(eventListeners['c']).not.toBeUndefined()
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(1);
|
||||||
|
expect(window.WailsInvoke).toHaveBeenLastCalledWith('EXa');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should cancel all event listeners for multiple types', () => {
|
||||||
|
EventsOff('a', 'b')
|
||||||
|
expect(eventListeners['a']).toBeUndefined()
|
||||||
|
expect(eventListeners['b']).toBeUndefined()
|
||||||
|
expect(eventListeners['c']).not.toBeUndefined()
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(2);
|
||||||
|
expect(window.WailsInvoke.calls).toStrictEqual([['EXa'], ['EXb']]);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('EventsOffAll', () => {
|
||||||
|
it('should cancel all event listeners', () => {
|
||||||
|
EventsOn('a', () => {})
|
||||||
|
EventsOn('a', () => {})
|
||||||
|
EventsOn('a', () => {})
|
||||||
|
EventsOn('b', () => {})
|
||||||
|
EventsOn('c', () => {})
|
||||||
|
EventsOffAll()
|
||||||
|
expect(eventListeners).toStrictEqual({})
|
||||||
|
expect(window.WailsInvoke).toBeCalledTimes(3);
|
||||||
|
expect(window.WailsInvoke.calls).toStrictEqual([['EXa'], ['EXb'], ['EXc']]);
|
||||||
|
})
|
||||||
|
})
|
23
v3/internal/runtime/desktop/ipc.js
Normal file
23
v3/internal/runtime/desktop/ipc.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WailsInvoke sends the given message to the backend
|
||||||
|
*
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
window.WailsInvoke = (message) => {
|
||||||
|
WINDOWS && window.chrome.webview.postMessage(message);
|
||||||
|
(DARWIN || LINUX) && window.webkit.messageHandlers.wails.postMessage(message);
|
||||||
|
}
|
||||||
|
})();
|
113
v3/internal/runtime/desktop/log.js
Normal file
113
v3/internal/runtime/desktop/log.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a log message to the backend with the given level + message
|
||||||
|
*
|
||||||
|
* @param {string} level
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
function sendLogMessage(level, message) {
|
||||||
|
|
||||||
|
// Log Message format:
|
||||||
|
// l[type][message]
|
||||||
|
window.WailsInvoke('L' + level + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given trace message with the backend
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function LogTrace(message) {
|
||||||
|
sendLogMessage('T', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given message with the backend
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function LogPrint(message) {
|
||||||
|
sendLogMessage('P', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given debug message with the backend
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function LogDebug(message) {
|
||||||
|
sendLogMessage('D', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given info message with the backend
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function LogInfo(message) {
|
||||||
|
sendLogMessage('I', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given warning message with the backend
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function LogWarning(message) {
|
||||||
|
sendLogMessage('W', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given error message with the backend
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function LogError(message) {
|
||||||
|
sendLogMessage('E', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given fatal message with the backend
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function LogFatal(message) {
|
||||||
|
sendLogMessage('F', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Log level to the given log level
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {number} loglevel
|
||||||
|
*/
|
||||||
|
export function SetLogLevel(loglevel) {
|
||||||
|
sendLogMessage('S', loglevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log levels
|
||||||
|
export const LogLevel = {
|
||||||
|
TRACE: 1,
|
||||||
|
DEBUG: 2,
|
||||||
|
INFO: 3,
|
||||||
|
WARNING: 4,
|
||||||
|
ERROR: 5,
|
||||||
|
};
|
80
v3/internal/runtime/desktop/main.js
Normal file
80
v3/internal/runtime/desktop/main.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
/* jshint esversion: 9 */
|
||||||
|
|
||||||
|
import "./ipc.js";
|
||||||
|
import {Callback, callbacks} from './calls';
|
||||||
|
import {EventsNotify, eventListeners} from "./events";
|
||||||
|
import {SetBindings} from "./bindings";
|
||||||
|
|
||||||
|
import * as Window from "./window";
|
||||||
|
import * as Screen from "./screen";
|
||||||
|
import * as Browser from "./browser";
|
||||||
|
import * as Log from './log';
|
||||||
|
|
||||||
|
let windowID = -1;
|
||||||
|
|
||||||
|
|
||||||
|
export function Quit() {
|
||||||
|
window.WailsInvoke('Q');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Show() {
|
||||||
|
window.WailsInvoke('S');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Hide() {
|
||||||
|
window.WailsInvoke('H');
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function Environment() {
|
||||||
|
// return Call(":wails:Environment");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Internal wails endpoints
|
||||||
|
window.wails = {
|
||||||
|
Callback,
|
||||||
|
callbacks,
|
||||||
|
EventsNotify,
|
||||||
|
eventListeners,
|
||||||
|
SetBindings,
|
||||||
|
window: {
|
||||||
|
ID: () => {
|
||||||
|
return windowID
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.runtime = {
|
||||||
|
...Log,
|
||||||
|
...Window,
|
||||||
|
...Browser,
|
||||||
|
...Screen,
|
||||||
|
EventsOn,
|
||||||
|
EventsOnce,
|
||||||
|
EventsOnMultiple,
|
||||||
|
EventsEmit,
|
||||||
|
EventsOff,
|
||||||
|
// Environment,
|
||||||
|
Show,
|
||||||
|
Hide,
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the expected runtime config from the backend
|
||||||
|
if( window.wails_config ) {
|
||||||
|
windowID = window.wails_config.windowID;
|
||||||
|
window.wails_config = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
console.log("Wails v3.0.0 Debug Mode Enabled");
|
||||||
|
}
|
||||||
|
|
25
v3/internal/runtime/desktop/screen.js
Normal file
25
v3/internal/runtime/desktop/screen.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint esversion: 9 */
|
||||||
|
|
||||||
|
|
||||||
|
import {Call} from "./calls";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||||
|
* @export
|
||||||
|
* @typedef {import('../wrapper/runtime').Screen} Screen
|
||||||
|
* @return {Promise<{Screen[]}>} The screens
|
||||||
|
*/
|
||||||
|
export function ScreenGetAll() {
|
||||||
|
return Call(":wails:ScreenGetAll");
|
||||||
|
}
|
269
v3/internal/runtime/desktop/window.js
Normal file
269
v3/internal/runtime/desktop/window.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint esversion: 9 */
|
||||||
|
|
||||||
|
|
||||||
|
import {Call} from "./calls";
|
||||||
|
|
||||||
|
export function WindowReload() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowReloadApp() {
|
||||||
|
window.WailsInvoke('WR');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetSystemDefaultTheme() {
|
||||||
|
window.WailsInvoke('WASDT');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetLightTheme() {
|
||||||
|
window.WailsInvoke('WALT');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetDarkTheme() {
|
||||||
|
window.WailsInvoke('WADT');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Place the window in the center of the screen
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowCenter() {
|
||||||
|
window.WailsInvoke('Wc');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the window title
|
||||||
|
*
|
||||||
|
* @param {string} title
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowSetTitle(title) {
|
||||||
|
window.WailsInvoke('WT' + title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the window go fullscreen
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowFullscreen() {
|
||||||
|
window.WailsInvoke('WF');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts the window from fullscreen
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowUnfullscreen() {
|
||||||
|
window.WailsInvoke('Wf');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @return {Promise<boolean>} The state of the window
|
||||||
|
*/
|
||||||
|
export function WindowIsFullscreen() {
|
||||||
|
return Call(":wails:WindowIsFullscreen");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Size of the window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {number} width
|
||||||
|
* @param {number} height
|
||||||
|
*/
|
||||||
|
export function WindowSetSize(width, height) {
|
||||||
|
window.WailsInvoke('Ws:' + width + ':' + height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Size of the window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @return {Promise<{w: number, h: number}>} The size of the window
|
||||||
|
|
||||||
|
*/
|
||||||
|
export function WindowGetSize() {
|
||||||
|
return Call(":wails:WindowGetSize");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the maximum size of the window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {number} width
|
||||||
|
* @param {number} height
|
||||||
|
*/
|
||||||
|
export function WindowSetMaxSize(width, height) {
|
||||||
|
window.WailsInvoke('WZ:' + width + ':' + height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the minimum size of the window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {number} width
|
||||||
|
* @param {number} height
|
||||||
|
*/
|
||||||
|
export function WindowSetMinSize(width, height) {
|
||||||
|
window.WailsInvoke('Wz:' + width + ':' + height);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the window AlwaysOnTop or not on top
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowSetAlwaysOnTop(b) {
|
||||||
|
|
||||||
|
window.WailsInvoke('WATP:' + (b ? '1' : '0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Position of the window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
|
export function WindowSetPosition(x, y) {
|
||||||
|
window.WailsInvoke('Wp:' + x + ':' + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Position of the window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @return {Promise<{x: number, y: number}>} The position of the window
|
||||||
|
*/
|
||||||
|
export function WindowGetPosition() {
|
||||||
|
return Call(":wails:WindowGetPos");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowHide() {
|
||||||
|
window.WailsInvoke('WH');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowShow() {
|
||||||
|
window.WailsInvoke('WS');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximise the Window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowMaximise() {
|
||||||
|
window.WailsInvoke('WM');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the Maximise of the Window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowToggleMaximise() {
|
||||||
|
window.WailsInvoke('Wt');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmaximise the Window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowUnmaximise() {
|
||||||
|
window.WailsInvoke('WU');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state of the window, i.e. whether the window is maximised or not.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @return {Promise<boolean>} The state of the window
|
||||||
|
*/
|
||||||
|
export function WindowIsMaximised() {
|
||||||
|
return Call(":wails:WindowIsMaximised");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimise the Window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowMinimise() {
|
||||||
|
window.WailsInvoke('Wm');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unminimise the Window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export function WindowUnminimise() {
|
||||||
|
window.WailsInvoke('Wu');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state of the window, i.e. whether the window is minimised or not.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @return {Promise<boolean>} The state of the window
|
||||||
|
*/
|
||||||
|
export function WindowIsMinimised() {
|
||||||
|
return Call(":wails:WindowIsMinimised");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state of the window, i.e. whether the window is normal or not.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @return {Promise<boolean>} The state of the window
|
||||||
|
*/
|
||||||
|
export function WindowIsNormal() {
|
||||||
|
return Call(":wails:WindowIsNormal");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the background colour of the window
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {number} R Red
|
||||||
|
* @param {number} G Green
|
||||||
|
* @param {number} B Blue
|
||||||
|
* @param {number} A Alpha
|
||||||
|
*/
|
||||||
|
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||||
|
let rgba = JSON.stringify({r: R || 0, g: G || 0, b: B || 0, a: A || 255});
|
||||||
|
window.WailsInvoke('Wr:' + rgba);
|
||||||
|
}
|
||||||
|
|
54
v3/internal/runtime/dev/Overlay.svelte
Normal file
54
v3/internal/runtime/dev/Overlay.svelte
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
import {overlayVisible} from './store'
|
||||||
|
import {fade,} from 'svelte/transition';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $overlayVisible }
|
||||||
|
<div class="wails-reconnect-overlay" transition:fade="{{ duration: 300 }}">
|
||||||
|
<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(2px) saturate(0%) contrast(50%) brightness(25%);
|
||||||
|
z-index: 999999
|
||||||
|
}
|
||||||
|
|
||||||
|
.wails-reconnect-overlay-content {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin: 0;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAA7CAMAAAAEsocZAAAC91BMVEUAAACzQ0PjMjLkMjLZLS7XLS+vJCjkMjKlEx6uGyHjMDGiFx7GJyrAISjUKy3mMzPlMjLjMzOsGyDKJirkMjK6HyXmMjLgMDC6IiLcMjLULC3MJyrRKSy+IibmMzPmMjK7ISXlMjLIJimzHSLkMjKtGiHZLC7BIifgMDCpGSDFIivcLy+yHSKoGR+eFBzNKCvlMjKxHSPkMTKxHSLmMjLKJyq5ICXDJCe6ISXdLzDkMjLmMzPFJSm2HyTlMTLhMDGyHSKUEBmhFx24HyTCJCjHJijjMzOiFh7mMjJ6BhDaLDCuGyOKABjnMzPGJinJJiquHCGEChSmGB/pMzOiFh7VKy3OKCu1HiSvHCLjMTLMKCrBIyeICxWxHCLDIyjSKizBIyh+CBO9ISa6ISWDChS9Iie1HyXVLC7FJSrLKCrlMjLiMTGPDhicFRywGyKXFBuhFx1/BxO7IiXkMTGeFBx8BxLkMTGnGR/GJCi4ICWsGyGJDxXSLS2yGiHSKi3CJCfnMzPQKiyECRTKJiq6ISWUERq/Iye0HiPDJCjGJSm6ICaPDxiTEBrdLy+3HyXSKiy0HyOQEBi4ICWhFh1+CBO9IieODhfSKyzWLC2LDhh8BxHKKCq7ISWaFBzkMzPqNDTTLC3EJSiHDBacExyvGyO1HyTPKCy+IieoGSC7ISaVEhrMKCvQKyusGyG0HiKACBPIJSq/JCaABxR5BRLEJCnkMzPJJinEJimPDRZ2BRKqHx/jMjLnMzPgMDHULC3NKSvQKSzsNDTWLS7SKyy3HyTKJyrDJSjbLzDYLC6mGB/GJSnVLC61HiPLKCrHJSm/Iye8Iia6ICWzHSKxHCLaLi/PKSupGR+7ICXpMzPbLi/IJinJJSmsGyGrGiCkFx6PDheJCxaFChXBIyfAIieSDxmBCBPlMjLeLzDdLzC5HySMDRe+ISWvGyGcFBzSKSzPJyvMJyrEJCjDIyefFRyWERriMDHUKiy/ISaZExv0NjbwNTXuNDTrMzMI0c+yAAAAu3RSTlMAA8HR/gwGgAj+MEpGCsC+hGpjQjYnIxgWBfzx7urizMrFqqB1bF83KhsR/fz8+/r5+fXv7unZ1tC+t6mmopqKdW1nYVpVRjUeHhIQBPr59/b28/Hx8ODg3NvUw8O/vKeim5aNioiDgn1vZWNjX1xUU1JPTUVFPT08Mi4qJyIh/Pv7+/n4+Pf39fT08/Du7efn5uXj4uHa19XNwsG/vrq2tbSuramlnpyYkpGNiIZ+enRraGVjVVBKOzghdjzRsAAABJVJREFUWMPtllVQG1EYhTc0ASpoobS0FCulUHd3oUjd3d3d3d3d3d2b7CYhnkBCCHGDEIK7Vh56d0NpOgwkYfLQzvA9ZrLfnPvfc+8uVEst/yheBJup3Nya2MjU6pa/jWLZtxjXpZFtVB4uVNI6m5gIruNkVFebqIb5Ug2ym4TIEM/gtUOGbg613oBzjAzZFrZ+lXu/3TIiMXXS5M6HTvrNHeLpZLEh6suGNW9fzZ9zd/qVi2eOHygqi5cDE5GUrJocONgzyqo0UXNSUlKSEhMztFqtXq9vNxImAmS3g7Y6QlbjdBWVGW36jt4wDGTUXjUsafh5zJWRkdFuZGtWGnCRmg+HasiGMUClTTzW0ZuVgLlGDIPM4Lhi0IrVq+tv2hS21fNrSONQgpM9DsJ4t3fM9PkvJuKj2ZjrZwvILKvaSTgciUSirjt6dOfOpyd169bDb9rMOwF9Hj4OD100gY0YXYb299bjzMrqj9doNByJWlVXFB9DT5dmJuvy+cq83JyuS6ayEYSHulKL8dmFnBkrCeZlHKMrC5XRhXGCZB2Ty1fkleRQaMCFT2DBsEafzRFJu7/2MicbKynPhQUDLiZwMWLJZKNLzoLbJBYVcurSmbmn+rcyJ8vCMgmlmaW6gnwun/+3C96VpAUuET1ZgRR36r2xWlnYSnf3oKABA14uXDDvydxHs6cpTV1p3hlJ2rJCiUjIZCByItXg8sHJijuvT64CuMTABUYvb6NN1Jdp1PH7D7f3bo2eS5KvW4RJr7atWT5w4MBBg9zdBw9+37BS7QIoFS5WnIaj12dr1DEXFgdvr4fh4eFl+u/wz8uf3jjHic8s4DL2Dal0IANyUBeCRCcwOBJV26JsjSpGwHVuSai69jvqD+jr56OgtKy0zAAK5mLTVBKVKL5tNthGAR9JneJQ/bFsHNzy+U7IlCYROxtMpIjR0ceoQVnowracLLpAQWETqV361bPoFo3cEbz2zYLZM7t3HWXcxmiBOgttS1ycWkTXMWh4mGigdug9DFdttqCFgTN6nD0q1XEVSoCxEjyFCi2eNC6Z69MRVIImJ6JQSf5gcFVCuF+aDhCa1F6MJFDaiNBQAh2TMfWBjhmLsAxUjG/fmjs0qjJck8D0GPBcuUuZW1LS/tIsPzqmQt17PvZQknlwnf4tHDBc+7t5VV3QQCkdc+Ur8/hdrz0but0RCumWiYbiKmLJ7EVbRomj4Q7+y5wsaXvfTGFpQcHB7n2WbG4MGdniw2Tm8xl5Yhr7MrSYHQ3uampz10aWyHyuzxvqaW/6W4MjXAUD3QV2aw97ZxhGjxCohYf5TpTHMXU1BbsAuoFnkRygVieIGAbqiF7rrH4rfWpKJouBCtyHJF8ctEyGubBa+C6NsMYEUonJFITHZqWBxXUA12Dv76Tf/PgOBmeNiiLG1pcKo1HAq8jLpY4JU1yWEixVNaOgoRJAKBSZHTZTU+wJOMtUDZvlVITC6FTlksyrEBoPHXpxxbzdaqzigUtVDkJVIOtVQ9UEOR4VGUh/kHWq0edJ6CxnZ+eePXva2bnY/cF/I1RLLf8vvwDANdMSMegxcAAAAABJRU5ErkJggg==);
|
||||||
|
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>
|
15
v3/internal/runtime/dev/build.js
Normal file
15
v3/internal/runtime/dev/build.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/* jshint esversion: 8 */
|
||||||
|
const esbuild = require("esbuild");
|
||||||
|
const sveltePlugin = require("esbuild-svelte");
|
||||||
|
|
||||||
|
esbuild
|
||||||
|
.build({
|
||||||
|
entryPoints: ["main.js"],
|
||||||
|
bundle: true,
|
||||||
|
minify: true,
|
||||||
|
outfile: "../ipc_websocket.js",
|
||||||
|
plugins: [sveltePlugin({compileOptions: {css: true}})],
|
||||||
|
logLevel: "info",
|
||||||
|
sourcemap: "inline",
|
||||||
|
})
|
||||||
|
.catch(() => process.exit(1));
|
8
v3/internal/runtime/dev/log.js
Normal file
8
v3/internal/runtime/dev/log.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export function log(message) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.log(
|
||||||
|
'%c wails dev %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'
|
||||||
|
);
|
||||||
|
}
|
125
v3/internal/runtime/dev/main.js
Normal file
125
v3/internal/runtime/dev/main.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
import {log} from "./log";
|
||||||
|
import Overlay from "./Overlay.svelte";
|
||||||
|
import {hideOverlay, showOverlay} from "./store";
|
||||||
|
|
||||||
|
let components = {};
|
||||||
|
|
||||||
|
let wailsInvokeInternal = null;
|
||||||
|
let messageQueue = [];
|
||||||
|
|
||||||
|
window.WailsInvoke = (message) => {
|
||||||
|
if (!wailsInvokeInternal) {
|
||||||
|
console.log("Queueing: " + message);
|
||||||
|
messageQueue.push(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wailsInvokeInternal(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
components.overlay = new Overlay({
|
||||||
|
target: document.body,
|
||||||
|
anchor: document.querySelector('#wails-spinner'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let websocket = null;
|
||||||
|
let connectTimer;
|
||||||
|
|
||||||
|
window.onbeforeunload = function () {
|
||||||
|
if (websocket) {
|
||||||
|
websocket.onclose = function () {
|
||||||
|
};
|
||||||
|
websocket.close();
|
||||||
|
websocket = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...and attempt to connect
|
||||||
|
connect();
|
||||||
|
|
||||||
|
function setupIPCBridge() {
|
||||||
|
wailsInvokeInternal = (message) => {
|
||||||
|
websocket.send(message);
|
||||||
|
};
|
||||||
|
for (let i = 0; i < messageQueue.length; i++) {
|
||||||
|
console.log("sending queued message: " + messageQueue[i]);
|
||||||
|
window.WailsInvoke(messageQueue[i]);
|
||||||
|
}
|
||||||
|
messageQueue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles incoming websocket connections
|
||||||
|
function handleConnect() {
|
||||||
|
log('Connected to backend');
|
||||||
|
hideOverlay();
|
||||||
|
setupIPCBridge();
|
||||||
|
clearInterval(connectTimer);
|
||||||
|
websocket.onclose = handleDisconnect;
|
||||||
|
websocket.onmessage = handleMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles websocket disconnects
|
||||||
|
function handleDisconnect() {
|
||||||
|
log('Disconnected from backend');
|
||||||
|
websocket = null;
|
||||||
|
showOverlay();
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _connect() {
|
||||||
|
if (websocket == null) {
|
||||||
|
websocket = new WebSocket('ws://' + window.location.host + '/wails/ipc');
|
||||||
|
websocket.onopen = handleConnect;
|
||||||
|
websocket.onerror = function (e) {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
websocket = null;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to connect to the backend every .5s
|
||||||
|
function connect() {
|
||||||
|
_connect();
|
||||||
|
connectTimer = setInterval(_connect, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessage(message) {
|
||||||
|
|
||||||
|
if (message.data === "reload") {
|
||||||
|
window.runtime.WindowReload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.data === "reloadapp") {
|
||||||
|
window.runtime.WindowReloadApp()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As a bridge we ignore js and css injections
|
||||||
|
switch (message.data[0]) {
|
||||||
|
// Notifications
|
||||||
|
case 'n':
|
||||||
|
window.wails.EventsNotify(message.data.slice(1));
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
const callbackData = message.data.slice(1);
|
||||||
|
window.wails.Callback(callbackData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log('Unknown message: ' + message.data);
|
||||||
|
}
|
||||||
|
}
|
1536
v3/internal/runtime/dev/package-lock.json
generated
Normal file
1536
v3/internal/runtime/dev/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
v3/internal/runtime/dev/package.json
Normal file
18
v3/internal/runtime/dev/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "dev",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"description": "Wails JS Dev",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "run-p build:*",
|
||||||
|
"build:dev": "node build.js"
|
||||||
|
},
|
||||||
|
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.12.17",
|
||||||
|
"esbuild-svelte": "^0.5.6",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"svelte": "^3.49.0"
|
||||||
|
}
|
||||||
|
}
|
12
v3/internal/runtime/dev/store.js
Normal file
12
v3/internal/runtime/dev/store.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import {writable} from 'svelte/store';
|
||||||
|
|
||||||
|
/** Overlay */
|
||||||
|
export const overlayVisible = writable(false);
|
||||||
|
|
||||||
|
export function showOverlay() {
|
||||||
|
overlayVisible.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideOverlay() {
|
||||||
|
overlayVisible.set(false);
|
||||||
|
}
|
9
v3/internal/runtime/ipc.go
Normal file
9
v3/internal/runtime/ipc.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed ipc_websocket.js
|
||||||
|
var WebsocketIPC []byte
|
||||||
|
|
||||||
|
//go:embed ipc.js
|
||||||
|
var DesktopIPC []byte
|
1
v3/internal/runtime/ipc.js
Normal file
1
v3/internal/runtime/ipc.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
(()=>{(function(){window.WailsInvoke=e=>{WINDOWS&&window.chrome.webview.postMessage(e),(DARWIN||LINUX)&&window.webkit.messageHandlers.wails.postMessage(e)}})();})();
|
22
v3/internal/runtime/ipc_websocket.js
Normal file
22
v3/internal/runtime/ipc_websocket.js
Normal file
File diff suppressed because one or more lines are too long
1503
v3/internal/runtime/package-lock.json
generated
Normal file
1503
v3/internal/runtime/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
v3/internal/runtime/package.json
Normal file
14
v3/internal/runtime/package.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "runtime",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"description": "Wails JS Runtime",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {},
|
||||||
|
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.15.6",
|
||||||
|
"happy-dom": "^8.1.3",
|
||||||
|
"vitest": "^0.24.3"
|
||||||
|
}
|
||||||
|
}
|
8
v3/internal/runtime/runtime_debug_darwin.go
Normal file
8
v3/internal/runtime/runtime_debug_darwin.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build darwin && !production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed runtime_debug_desktop_darwin.js
|
||||||
|
var DesktopRuntime []byte
|
420
v3/internal/runtime/runtime_debug_desktop_darwin.js
Normal file
420
v3/internal/runtime/runtime_debug_desktop_darwin.js
Normal file
File diff suppressed because one or more lines are too long
420
v3/internal/runtime/runtime_debug_desktop_linux.js
Normal file
420
v3/internal/runtime/runtime_debug_desktop_linux.js
Normal file
File diff suppressed because one or more lines are too long
420
v3/internal/runtime/runtime_debug_desktop_windows.js
Normal file
420
v3/internal/runtime/runtime_debug_desktop_windows.js
Normal file
File diff suppressed because one or more lines are too long
8
v3/internal/runtime/runtime_debug_linux.go
Normal file
8
v3/internal/runtime/runtime_debug_linux.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build linux && !production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed runtime_debug_desktop_linux.js
|
||||||
|
var DesktopRuntime []byte
|
8
v3/internal/runtime/runtime_debug_windows.go
Normal file
8
v3/internal/runtime/runtime_debug_windows.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build windows && !production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed runtime_debug_desktop_windows.js
|
||||||
|
var DesktopRuntime []byte
|
8
v3/internal/runtime/runtime_production_darwin.go
Normal file
8
v3/internal/runtime/runtime_production_darwin.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build darwin && production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed runtime_production_desktop_darwin.js
|
||||||
|
var DesktopRuntime []byte
|
1
v3/internal/runtime/runtime_production_desktop_darwin.js
Normal file
1
v3/internal/runtime/runtime_production_desktop_darwin.js
Normal file
File diff suppressed because one or more lines are too long
1
v3/internal/runtime/runtime_production_desktop_linux.js
Normal file
1
v3/internal/runtime/runtime_production_desktop_linux.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
v3/internal/runtime/runtime_production_linux.go
Normal file
8
v3/internal/runtime/runtime_production_linux.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build linux && production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed runtime_production_desktop_linux.js
|
||||||
|
var DesktopRuntime []byte
|
8
v3/internal/runtime/runtime_production_windows.go
Normal file
8
v3/internal/runtime/runtime_production_windows.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build windows && production
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed runtime_production_desktop_windows.js
|
||||||
|
var DesktopRuntime []byte
|
7
v3/internal/runtime/vite.config.ts
Normal file
7
v3/internal/runtime/vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'happy-dom',
|
||||||
|
},
|
||||||
|
})
|
10
v3/pkg/application/TODO.md
Normal file
10
v3/pkg/application/TODO.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
Features
|
||||||
|
- [ ] AssetServer
|
||||||
|
- [ ] Offline page if navigating to external URL
|
||||||
|
- [x] Application menu
|
||||||
|
|
||||||
|
Bugs
|
||||||
|
- [ ] Resize Window
|
||||||
|
- [ ] Fullscreen/Maximise/Minimise/Restore
|
||||||
|
|
12
v3/pkg/application/app_delegate.h
Normal file
12
v3/pkg/application/app_delegate.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
#ifndef appdelegate_h
|
||||||
|
#define appdelegate_h
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
|
@property bool shouldTerminateWhenLastWindowClosed;
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif
|
142
v3/pkg/application/app_delegate.m
Normal file
142
v3/pkg/application/app_delegate.m
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
#import "app_delegate.h"
|
||||||
|
#import "../events/events.h"
|
||||||
|
|
||||||
|
extern bool hasListeners(unsigned int);
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the applicationShouldTerminateAfterLastWindowClosed: method
|
||||||
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
|
||||||
|
{
|
||||||
|
return self.shouldTerminateWhenLastWindowClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GENERATED EVENTS START
|
||||||
|
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidBecomeActive) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidBecomeActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidChangeBackingProperties:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidChangeBackingProperties) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidChangeBackingProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidChangeEffectiveAppearance:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidChangeEffectiveAppearance) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidChangeEffectiveAppearance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidChangeIcon:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidChangeIcon) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidChangeIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidChangeOcclusionState:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidChangeOcclusionState) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidChangeOcclusionState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidChangeScreenParameters:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidChangeScreenParameters) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidChangeScreenParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidChangeStatusBarFrame:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidChangeStatusBarFrame) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidChangeStatusBarFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidChangeStatusBarOrientation:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidChangeStatusBarOrientation) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidChangeStatusBarOrientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidFinishLaunching) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidFinishLaunching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidHide:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidHide) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidHide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidResignActive:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidResignActive) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidResignActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidUnhide:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidUnhide) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidUnhide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidUpdate:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationDidUpdate) ) {
|
||||||
|
processApplicationEvent(EventApplicationDidUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillBecomeActive:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationWillBecomeActive) ) {
|
||||||
|
processApplicationEvent(EventApplicationWillBecomeActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationWillFinishLaunching) ) {
|
||||||
|
processApplicationEvent(EventApplicationWillFinishLaunching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillHide:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationWillHide) ) {
|
||||||
|
processApplicationEvent(EventApplicationWillHide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationWillResignActive) ) {
|
||||||
|
processApplicationEvent(EventApplicationWillResignActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillTerminate:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationWillTerminate) ) {
|
||||||
|
processApplicationEvent(EventApplicationWillTerminate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationWillUnhide) ) {
|
||||||
|
processApplicationEvent(EventApplicationWillUnhide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillUpdate:(NSNotification *)notification {
|
||||||
|
if( hasListeners(EventApplicationWillUpdate) ) {
|
||||||
|
processApplicationEvent(EventApplicationWillUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GENERATED EVENTS END
|
||||||
|
@end
|
384
v3/pkg/application/application.go
Normal file
384
v3/pkg/application/application.go
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/options"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalApplication *App
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(appOptions options.Application) *App {
|
||||||
|
if globalApplication != nil {
|
||||||
|
return globalApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeApplicationDefaults(&appOptions)
|
||||||
|
|
||||||
|
result := &App{
|
||||||
|
options: appOptions,
|
||||||
|
applicationEventListeners: make(map[uint][]func()),
|
||||||
|
systemTrays: make(map[uint]*SystemTray),
|
||||||
|
}
|
||||||
|
globalApplication = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeApplicationDefaults(o *options.Application) {
|
||||||
|
if o.Name == "" {
|
||||||
|
o.Name = "My Wails Application"
|
||||||
|
}
|
||||||
|
if o.Description == "" {
|
||||||
|
o.Description = "An application written using Wails"
|
||||||
|
}
|
||||||
|
if o.Icon == nil {
|
||||||
|
o.Icon = DefaultApplicationIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type platformApp interface {
|
||||||
|
run() error
|
||||||
|
destroy()
|
||||||
|
setApplicationMenu(menu *Menu)
|
||||||
|
name() string
|
||||||
|
getCurrentWindowID() uint
|
||||||
|
showAboutDialog(name string, description string, icon []byte)
|
||||||
|
setIcon(icon []byte)
|
||||||
|
on(id uint)
|
||||||
|
dispatchOnMainThread(id uint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages sent from javascript get routed here
|
||||||
|
type windowMessage struct {
|
||||||
|
windowId uint
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
var windowMessageBuffer = make(chan *windowMessage)
|
||||||
|
|
||||||
|
type webViewAssetRequest struct {
|
||||||
|
windowId uint
|
||||||
|
request webview.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
var webviewRequests = make(chan *webViewAssetRequest)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
options options.Application
|
||||||
|
applicationEventListeners map[uint][]func()
|
||||||
|
applicationEventListenersLock sync.RWMutex
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
windows map[uint]*WebviewWindow
|
||||||
|
windowsLock sync.Mutex
|
||||||
|
windowAliases map[string]uint
|
||||||
|
windowAliasesLock sync.Mutex
|
||||||
|
|
||||||
|
// System Trays
|
||||||
|
systemTrays map[uint]*SystemTray
|
||||||
|
systemTraysLock sync.Mutex
|
||||||
|
systemTrayID uint
|
||||||
|
systemTrayIDLock sync.RWMutex
|
||||||
|
|
||||||
|
// MenuItems
|
||||||
|
menuItems map[uint]*MenuItem
|
||||||
|
menuItemsLock sync.Mutex
|
||||||
|
|
||||||
|
// Running
|
||||||
|
running bool
|
||||||
|
|
||||||
|
// platform app
|
||||||
|
impl platformApp
|
||||||
|
|
||||||
|
// The main application menu
|
||||||
|
ApplicationMenu *Menu
|
||||||
|
|
||||||
|
// About MessageDialog
|
||||||
|
clipboard *Clipboard
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) getSystemTrayID() uint {
|
||||||
|
a.systemTrayIDLock.Lock()
|
||||||
|
defer a.systemTrayIDLock.Unlock()
|
||||||
|
a.systemTrayID++
|
||||||
|
return a.systemTrayID
|
||||||
|
}
|
||||||
|
func (a *App) On(eventType events.ApplicationEventType, callback func()) {
|
||||||
|
eventID := uint(eventType)
|
||||||
|
a.applicationEventListenersLock.Lock()
|
||||||
|
defer a.applicationEventListenersLock.Unlock()
|
||||||
|
a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], callback)
|
||||||
|
if a.impl != nil {
|
||||||
|
a.impl.on(eventID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (a *App) NewWebviewWindow() *WebviewWindow {
|
||||||
|
return a.NewWebviewWindowWithOptions(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) NewWebviewWindowWithOptions(windowOptions *options.WebviewWindow) *WebviewWindow {
|
||||||
|
// Ensure we have sane defaults
|
||||||
|
if windowOptions == nil {
|
||||||
|
windowOptions = options.WindowDefaults
|
||||||
|
}
|
||||||
|
|
||||||
|
newWindow := NewWindow(windowOptions)
|
||||||
|
id := newWindow.id
|
||||||
|
if a.windows == nil {
|
||||||
|
a.windows = make(map[uint]*WebviewWindow)
|
||||||
|
}
|
||||||
|
a.windowsLock.Lock()
|
||||||
|
a.windows[id] = newWindow
|
||||||
|
a.windowsLock.Unlock()
|
||||||
|
|
||||||
|
if windowOptions.Alias != "" {
|
||||||
|
if a.windowAliases == nil {
|
||||||
|
a.windowAliases = make(map[string]uint)
|
||||||
|
}
|
||||||
|
a.windowAliasesLock.Lock()
|
||||||
|
a.windowAliases[windowOptions.Alias] = id
|
||||||
|
a.windowAliasesLock.Unlock()
|
||||||
|
}
|
||||||
|
if a.running {
|
||||||
|
newWindow.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
return newWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) NewSystemTray() *SystemTray {
|
||||||
|
|
||||||
|
id := a.getSystemTrayID()
|
||||||
|
newSystemTray := NewSystemTray(id)
|
||||||
|
a.systemTraysLock.Lock()
|
||||||
|
a.systemTrays[id] = newSystemTray
|
||||||
|
a.systemTraysLock.Unlock()
|
||||||
|
|
||||||
|
if a.running {
|
||||||
|
newSystemTray.Run()
|
||||||
|
}
|
||||||
|
return newSystemTray
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Run() error {
|
||||||
|
a.impl = newPlatformApp(a)
|
||||||
|
|
||||||
|
a.running = true
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
event := <-applicationEvents
|
||||||
|
a.handleApplicationEvent(event)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
event := <-windowEvents
|
||||||
|
a.handleWindowEvent(event)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
event := <-webviewRequests
|
||||||
|
a.handleWebViewRequest(event)
|
||||||
|
event.request.Release()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
event := <-windowMessageBuffer
|
||||||
|
a.handleWindowMessage(event)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
menuItemID := <-menuItemClicked
|
||||||
|
a.handleMenuItemClicked(menuItemID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// run windows
|
||||||
|
for _, window := range a.windows {
|
||||||
|
go window.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// run system trays
|
||||||
|
for _, systray := range a.systemTrays {
|
||||||
|
go systray.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the application menu
|
||||||
|
a.impl.setApplicationMenu(a.ApplicationMenu)
|
||||||
|
|
||||||
|
// set the application icon
|
||||||
|
a.impl.setIcon(a.options.Icon)
|
||||||
|
|
||||||
|
return a.impl.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleApplicationEvent(event uint) {
|
||||||
|
a.applicationEventListenersLock.RLock()
|
||||||
|
listeners, ok := a.applicationEventListeners[event]
|
||||||
|
a.applicationEventListenersLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, listener := range listeners {
|
||||||
|
go listener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleWindowMessage(event *windowMessage) {
|
||||||
|
// Get window from window map
|
||||||
|
a.windowsLock.Lock()
|
||||||
|
window, ok := a.windows[event.windowId]
|
||||||
|
a.windowsLock.Unlock()
|
||||||
|
if !ok {
|
||||||
|
log.Printf("WebviewWindow #%d not found", event.windowId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get callback from window
|
||||||
|
window.handleMessage(event.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleWebViewRequest(event *webViewAssetRequest) {
|
||||||
|
// Get window from window map
|
||||||
|
a.windowsLock.Lock()
|
||||||
|
window, ok := a.windows[event.windowId]
|
||||||
|
a.windowsLock.Unlock()
|
||||||
|
if !ok {
|
||||||
|
log.Printf("WebviewWindow #%d not found", event.windowId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get callback from window
|
||||||
|
window.handleWebViewRequest(event.request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleWindowEvent(event *WindowEvent) {
|
||||||
|
// Get window from window map
|
||||||
|
a.windowsLock.Lock()
|
||||||
|
window, ok := a.windows[event.WindowID]
|
||||||
|
a.windowsLock.Unlock()
|
||||||
|
if !ok {
|
||||||
|
log.Printf("WebviewWindow #%d not found", event.WindowID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.handleWindowEvent(event.EventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleMenuItemClicked(menuItemID uint) {
|
||||||
|
menuItem := getMenuItemByID(menuItemID)
|
||||||
|
if menuItem == nil {
|
||||||
|
log.Printf("MenuItem #%d not found", menuItemID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
menuItem.handleClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) CurrentWindow() *WebviewWindow {
|
||||||
|
if a.impl == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id := a.impl.getCurrentWindowID()
|
||||||
|
a.windowsLock.Lock()
|
||||||
|
defer a.windowsLock.Unlock()
|
||||||
|
return a.windows[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Quit() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
a.windowsLock.Lock()
|
||||||
|
for _, window := range a.windows {
|
||||||
|
window.Destroy()
|
||||||
|
}
|
||||||
|
a.windowsLock.Unlock()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
a.systemTraysLock.Lock()
|
||||||
|
for _, systray := range a.systemTrays {
|
||||||
|
systray.Destroy()
|
||||||
|
}
|
||||||
|
a.systemTraysLock.Unlock()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
a.impl.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SetMenu(menu *Menu) {
|
||||||
|
a.ApplicationMenu = menu
|
||||||
|
if a.impl != nil {
|
||||||
|
a.impl.setApplicationMenu(menu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (a *App) ShowAboutDialog() {
|
||||||
|
if a.impl != nil {
|
||||||
|
a.impl.showAboutDialog(a.options.Name, a.options.Description, a.options.Icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) InfoDialog() *MessageDialog {
|
||||||
|
return newMessageDialog(InfoDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) QuestionDialog() *MessageDialog {
|
||||||
|
return newMessageDialog(QuestionDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) WarningDialog() *MessageDialog {
|
||||||
|
return newMessageDialog(WarningDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) ErrorDialog() *MessageDialog {
|
||||||
|
return newMessageDialog(ErrorDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) OpenDirectoryDialog() *MessageDialog {
|
||||||
|
return newMessageDialog(OpenDirectoryDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) OpenFileDialog() *OpenFileDialog {
|
||||||
|
return newOpenFileDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SaveFileDialog() *SaveFileDialog {
|
||||||
|
return newSaveFileDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetPrimaryScreen() (*Screen, error) {
|
||||||
|
return getPrimaryScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetScreens() ([]*Screen, error) {
|
||||||
|
return getScreens()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Clipboard() *Clipboard {
|
||||||
|
if a.clipboard == nil {
|
||||||
|
a.clipboard = newClipboard()
|
||||||
|
}
|
||||||
|
return a.clipboard
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) dispatchOnMainThread(fn func()) {
|
||||||
|
mainThreadFunctionStoreLock.Lock()
|
||||||
|
id := generateFunctionStoreID()
|
||||||
|
mainThreadFunctionStore[id] = fn
|
||||||
|
mainThreadFunctionStoreLock.Unlock()
|
||||||
|
// Call platform specific dispatch function
|
||||||
|
a.impl.dispatchOnMainThread(id)
|
||||||
|
}
|
11
v3/pkg/application/application.h
Normal file
11
v3/pkg/application/application.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
#ifndef application_h
|
||||||
|
#define application_h
|
||||||
|
|
||||||
|
static void init(void);
|
||||||
|
static void run(void);
|
||||||
|
static void setActivationPolicy(int policy);
|
||||||
|
static char *getAppName(void);
|
||||||
|
|
||||||
|
#endif
|
225
v3/pkg/application/application_darwin.go
Normal file
225
v3/pkg/application/application_darwin.go
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package application
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
|
||||||
|
#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13
|
||||||
|
|
||||||
|
#include "application.h"
|
||||||
|
#include "app_delegate.h"
|
||||||
|
#include "webview_window.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
extern void registerListener(unsigned int event);
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
static AppDelegate *appDelegate = nil;
|
||||||
|
|
||||||
|
static void init(void) {
|
||||||
|
[NSApplication sharedApplication];
|
||||||
|
appDelegate = [[AppDelegate alloc] init];
|
||||||
|
[NSApp setDelegate:appDelegate];
|
||||||
|
|
||||||
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
||||||
|
NSWindow* eventWindow = [event window];
|
||||||
|
if (eventWindow == nil ) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate];
|
||||||
|
if (windowDelegate == nil) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
if ([windowDelegate respondsToSelector:@selector(handleLeftMouseDown:)]) {
|
||||||
|
[windowDelegate handleLeftMouseDown:event];
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}];
|
||||||
|
|
||||||
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
||||||
|
NSWindow* eventWindow = [event window];
|
||||||
|
if (eventWindow == nil ) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate];
|
||||||
|
if (windowDelegate == nil) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
if ([windowDelegate respondsToSelector:@selector(handleLeftMouseUp:)]) {
|
||||||
|
[windowDelegate handleLeftMouseUp:eventWindow];
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setApplicationShouldTerminateAfterLastWindowClosed(bool shouldTerminate) {
|
||||||
|
// Get the NSApp delegate
|
||||||
|
AppDelegate *appDelegate = (AppDelegate*)[NSApp delegate];
|
||||||
|
// Set the applicationShouldTerminateAfterLastWindowClosed boolean
|
||||||
|
appDelegate.shouldTerminateWhenLastWindowClosed = shouldTerminate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setActivationPolicy(int policy) {
|
||||||
|
[NSApp setActivationPolicy:policy];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void activateIgnoringOtherApps() {
|
||||||
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run(void) {
|
||||||
|
@autoreleasepool {
|
||||||
|
[NSApp run];
|
||||||
|
[appDelegate release];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy application
|
||||||
|
static void destroyApp(void) {
|
||||||
|
[NSApp terminate:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the application menu
|
||||||
|
static void setApplicationMenu(void *menu) {
|
||||||
|
NSMenu *nsMenu = (__bridge NSMenu *)menu;
|
||||||
|
[NSApp setMainMenu:menu];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the application name
|
||||||
|
static char* getAppName(void) {
|
||||||
|
NSString *appName = [NSRunningApplication currentApplication].localizedName;
|
||||||
|
if( appName == nil ) {
|
||||||
|
appName = [[NSProcessInfo processInfo] processName];
|
||||||
|
}
|
||||||
|
return strdup([appName UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the current window ID
|
||||||
|
static unsigned int getCurrentWindowID(void) {
|
||||||
|
NSWindow *window = [NSApp keyWindow];
|
||||||
|
// Get the window delegate
|
||||||
|
WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)[window delegate];
|
||||||
|
return delegate.windowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the application icon
|
||||||
|
static void setApplicationIcon(void *icon, int length) {
|
||||||
|
// On main thread
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]];
|
||||||
|
[NSApp setApplicationIconImage:image];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
type macosApp struct {
|
||||||
|
applicationMenu unsafe.Pointer
|
||||||
|
parent *App
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *macosApp) on(eventID uint) {
|
||||||
|
C.registerListener(C.uint(eventID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *macosApp) setIcon(icon []byte) {
|
||||||
|
C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *macosApp) name() string {
|
||||||
|
appName := C.getAppName()
|
||||||
|
defer C.free(unsafe.Pointer(appName))
|
||||||
|
return C.GoString(appName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *macosApp) getCurrentWindowID() uint {
|
||||||
|
return uint(C.getCurrentWindowID())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *macosApp) setApplicationMenu(menu *Menu) {
|
||||||
|
if menu == nil {
|
||||||
|
// Create a default menu for mac
|
||||||
|
menu = defaultApplicationMenu()
|
||||||
|
}
|
||||||
|
menu.Update()
|
||||||
|
|
||||||
|
// Convert impl to macosMenu object
|
||||||
|
m.applicationMenu = (menu.impl).(*macosMenu).nsMenu
|
||||||
|
C.setApplicationMenu(m.applicationMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *macosApp) run() error {
|
||||||
|
// Add a hook to the ApplicationDidFinishLaunching event
|
||||||
|
m.parent.On(events.Mac.ApplicationDidFinishLaunching, func() {
|
||||||
|
C.setApplicationShouldTerminateAfterLastWindowClosed(C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed))
|
||||||
|
C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy))
|
||||||
|
C.activateIgnoringOtherApps()
|
||||||
|
})
|
||||||
|
// setup event listeners
|
||||||
|
for eventID := range m.parent.applicationEventListeners {
|
||||||
|
m.on(eventID)
|
||||||
|
}
|
||||||
|
C.run()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *macosApp) destroy() {
|
||||||
|
C.destroyApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPlatformApp(app *App) *macosApp {
|
||||||
|
C.init()
|
||||||
|
return &macosApp{
|
||||||
|
parent: app,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export processApplicationEvent
|
||||||
|
func processApplicationEvent(eventID C.uint) {
|
||||||
|
applicationEvents <- uint(eventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export processWindowEvent
|
||||||
|
func processWindowEvent(windowID C.uint, eventID C.uint) {
|
||||||
|
windowEvents <- &WindowEvent{
|
||||||
|
WindowID: uint(windowID),
|
||||||
|
EventID: uint(eventID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export processMessage
|
||||||
|
func processMessage(windowID C.uint, message *C.char) {
|
||||||
|
windowMessageBuffer <- &windowMessage{
|
||||||
|
windowId: uint(windowID),
|
||||||
|
message: C.GoString(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export processURLRequest
|
||||||
|
func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) {
|
||||||
|
webviewRequests <- &webViewAssetRequest{
|
||||||
|
windowId: uint(windowID),
|
||||||
|
request: webview.NewRequest(wkUrlSchemeTask),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export processMenuItemClick
|
||||||
|
func processMenuItemClick(menuID C.uint) {
|
||||||
|
menuItemClicked <- uint(menuID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIcon(icon []byte) {
|
||||||
|
if icon == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon)))
|
||||||
|
}
|
26
v3/pkg/application/clipboard.go
Normal file
26
v3/pkg/application/clipboard.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type clipboardImpl interface {
|
||||||
|
setText(text string) bool
|
||||||
|
text() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Clipboard struct {
|
||||||
|
impl clipboardImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClipboard() *Clipboard {
|
||||||
|
return &Clipboard{
|
||||||
|
impl: newClipboardImpl(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Clipboard) SetText(text string) bool {
|
||||||
|
return c.impl.setText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Clipboard) Text() string {
|
||||||
|
return c.impl.text()
|
||||||
|
}
|
56
v3/pkg/application/clipboard_darwin.go
Normal file
56
v3/pkg/application/clipboard_darwin.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package application
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
|
||||||
|
#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <stdlib.h>
|
||||||
|
|
||||||
|
bool setClipboardText(const char* text) {
|
||||||
|
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
|
||||||
|
NSError *error = nil;
|
||||||
|
NSString *string = [NSString stringWithUTF8String:text];
|
||||||
|
[pasteBoard clearContents];
|
||||||
|
return [pasteBoard setString:string forType:NSPasteboardTypeString];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getClipboardText() {
|
||||||
|
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
NSString *text = [pasteboard stringForType:NSPasteboardTypeString];
|
||||||
|
return [text UTF8String];
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clipboardLock sync.RWMutex
|
||||||
|
|
||||||
|
type macosClipboard struct{}
|
||||||
|
|
||||||
|
func (m macosClipboard) setText(text string) bool {
|
||||||
|
clipboardLock.Lock()
|
||||||
|
defer clipboardLock.Unlock()
|
||||||
|
cText := C.CString(text)
|
||||||
|
success := C.setClipboardText(cText)
|
||||||
|
C.free(unsafe.Pointer(cText))
|
||||||
|
return bool(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m macosClipboard) text() string {
|
||||||
|
clipboardLock.RLock()
|
||||||
|
defer clipboardLock.RUnlock()
|
||||||
|
clipboardText := C.getClipboardText()
|
||||||
|
result := C.GoString(clipboardText)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClipboardImpl() *macosClipboard {
|
||||||
|
return &macosClipboard{}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user